activemerchant 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +58 -0
  3. data/CONTRIBUTERS +25 -0
  4. data/MIT-LICENSE +3 -3
  5. data/README +16 -10
  6. data/Rakefile +4 -3
  7. data/lib/active_merchant.rb +7 -1
  8. data/lib/active_merchant/billing/check.rb +16 -9
  9. data/lib/active_merchant/billing/gateway.rb +1 -1
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +702 -0
  11. data/lib/active_merchant/billing/gateways/beanstream.rb +102 -0
  12. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +233 -0
  13. data/lib/active_merchant/billing/gateways/beanstream_interac.rb +54 -0
  14. data/lib/active_merchant/billing/gateways/braintree.rb +10 -1
  15. data/lib/active_merchant/billing/gateways/cyber_source.rb +26 -2
  16. data/lib/active_merchant/billing/gateways/data_cash.rb +255 -59
  17. data/lib/active_merchant/billing/gateways/modern_payments.rb +36 -0
  18. data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +214 -0
  19. data/lib/active_merchant/billing/gateways/net_registry.rb +1 -0
  20. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +2 -2
  21. data/lib/active_merchant/billing/gateways/payflow_express.rb +3 -11
  22. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  23. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +39 -21
  24. data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
  25. data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -12
  26. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +20 -0
  27. data/lib/active_merchant/billing/gateways/protx.rb +25 -25
  28. data/lib/active_merchant/billing/gateways/sage.rb +145 -0
  29. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +88 -0
  30. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +110 -0
  31. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +97 -0
  32. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +3 -1
  33. data/lib/active_merchant/billing/gateways/skip_jack.rb +2 -0
  34. data/lib/active_merchant/billing/gateways/trust_commerce.rb +1 -1
  35. data/lib/active_merchant/billing/gateways/wirecard.rb +304 -0
  36. data/lib/active_merchant/billing/integrations.rb +8 -2
  37. data/lib/active_merchant/billing/integrations/action_view_helper.rb +18 -4
  38. data/lib/active_merchant/billing/integrations/hi_trust/notification.rb +4 -2
  39. data/lib/active_merchant/billing/integrations/notification.rb +10 -1
  40. data/lib/active_merchant/lib/posts_data.rb +12 -3
  41. data/script/destroy +0 -0
  42. data/script/generate +0 -0
  43. data/test/extra/binding_of_caller.rb +0 -0
  44. data/test/extra/breakpoint.rb +0 -0
  45. data/test/fixtures.yml +24 -0
  46. data/test/remote/gateways/remote_authorize_net_cim_test.rb +459 -0
  47. data/test/remote/gateways/remote_beanstream_interac_test.rb +53 -0
  48. data/test/remote/gateways/remote_beanstream_test.rb +150 -0
  49. data/test/remote/gateways/remote_braintree_test.rb +22 -0
  50. data/test/remote/gateways/remote_cyber_source_test.rb +28 -3
  51. data/test/remote/gateways/remote_data_cash_test.rb +250 -48
  52. data/test/remote/gateways/remote_modern_payments_cim_test.rb +58 -0
  53. data/test/remote/gateways/remote_modern_payments_test.rb +43 -0
  54. data/test/remote/gateways/remote_sage_bankcard_test.rb +109 -0
  55. data/test/remote/gateways/remote_sage_test.rb +87 -0
  56. data/test/remote/gateways/remote_sage_virtual_check_test.rb +62 -0
  57. data/test/remote/gateways/remote_wirecard_test.rb +76 -0
  58. data/test/remote/integrations/remote_paypal_integration_test.rb +15 -3
  59. data/test/test_helper.rb +31 -13
  60. data/test/unit/check_test.rb +14 -2
  61. data/test/unit/credit_card_methods_test.rb +18 -0
  62. data/test/unit/gateways/authorize_net_cim_test.rb +641 -0
  63. data/test/unit/gateways/beanstream_interac_test.rb +51 -0
  64. data/test/unit/gateways/beanstream_test.rb +108 -0
  65. data/test/unit/gateways/braintree_test.rb +2 -5
  66. data/test/unit/gateways/cyber_source_test.rb +18 -0
  67. data/test/unit/gateways/data_cash_test.rb +32 -4
  68. data/test/unit/gateways/gateway_test.rb +8 -1
  69. data/test/unit/gateways/modern_payments_cim_test.rb +171 -0
  70. data/test/unit/gateways/net_registry_test.rb +6 -0
  71. data/test/unit/gateways/payflow_express_test.rb +18 -2
  72. data/test/unit/gateways/paypal_express_test.rb +154 -0
  73. data/test/unit/gateways/paypal_test.rb +140 -0
  74. data/test/unit/gateways/sage_bankcard_test.rb +162 -0
  75. data/test/unit/gateways/sage_virtual_check_test.rb +71 -0
  76. data/test/unit/gateways/secure_pay_au_test.rb +58 -1
  77. data/test/unit/gateways/skip_jack_test.rb +8 -0
  78. data/test/unit/gateways/verifi_test.rb +0 -1
  79. data/test/unit/gateways/wirecard_test.rb +232 -0
  80. data/test/unit/integrations/action_view_helper_test.rb +3 -0
  81. data/test/unit/integrations/notifications/hi_trust_notification_test.rb +23 -2
  82. data/test/unit/integrations/notifications/notification_test.rb +13 -0
  83. data/test/unit/posts_data_test.rb +20 -6
  84. metadata +40 -5
  85. metadata.gz.sig +0 -0
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/sage_core'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class SageVirtualCheckGateway < Gateway #:nodoc:
6
+ include SageCore
7
+ self.url = 'https://www.sagepayments.net/cgi-bin/eftVirtualCheck.dll?transaction'
8
+ self.source = 'virtual_check'
9
+
10
+ def purchase(money, credit_card, options = {})
11
+ post = {}
12
+ add_check(post, credit_card)
13
+ add_check_customer_data(post, options)
14
+ add_transaction_data(post, money, options)
15
+ commit(:purchase, post)
16
+ end
17
+
18
+ def void(reference, options = {})
19
+ post = {}
20
+ add_reference(post, reference)
21
+ commit(:void, post)
22
+ end
23
+
24
+ def credit(money, credit_card, options = {})
25
+ post = {}
26
+ add_check(post, credit_card)
27
+ add_check_customer_data(post, options)
28
+ add_transaction_data(post, money, options)
29
+ commit(:credit, post)
30
+ end
31
+
32
+ private
33
+ def add_check(post, check)
34
+ post[:C_first_name] = check.first_name
35
+ post[:C_last_name] = check.last_name
36
+ post[:C_rte] = check.routing_number
37
+ post[:C_acct] = check.account_number
38
+ post[:C_check_number] = check.number
39
+ post[:C_acct_type] = account_type(check)
40
+ end
41
+
42
+ def add_check_customer_data(post, options)
43
+ # Required  Customer Type – (NACHA Transaction Class)
44
+ # CCD for Commercial, Merchant Initiated
45
+ # PPD for Personal, Merchant Initiated
46
+ # WEB for Internet, Consumer Initiated
47
+ # RCK for Returned Checks
48
+ # ARC for Account Receivable Entry
49
+ # TEL for TelephoneInitiated
50
+ post[:C_customer_type] = "WEB"
51
+
52
+ # Optional  10  Digit Originator  ID – Assigned  By for  each transaction  class  or  business  purpose. If  not provided, the default Originator ID for the specific  Customer Type will be applied. 
53
+ post[:C_originator_id] = options[:originator_id]
54
+
55
+ # Optional  Transaction Addenda
56
+ post[:T_addenda] = options[:addenda]
57
+
58
+ # Required  Check  Writer  Social  Security  Number  (  Numbers Only, No Dashes ) 
59
+ post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '')
60
+
61
+ post[:C_dl_state_code] = options[:drivers_license_state]
62
+ post[:C_dl_number] = options[:drivers_license_number]
63
+ post[:C_dob] = format_birth_date(options[:date_of_birth])
64
+ end
65
+
66
+ def format_birth_date(date)
67
+ date.respond_to?(:strftime) ? date.strftime("%m/%d/%Y") : date
68
+ end
69
+
70
+ # DDA for Checking
71
+ # SAV for Savings 
72
+ def account_type(check)
73
+ case check.account_type
74
+ when 'checking' then 'DDA'
75
+ when 'savings' then 'SAV'
76
+ else raise ArgumentError, "Unknown account type #{check.account_type}"
77
+ end
78
+ end
79
+
80
+ def parse(data)
81
+ response = {}
82
+ response[:success] = data[1,1]
83
+ response[:code] = data[2,6].strip
84
+ response[:message] = data[8,32].strip
85
+ response[:risk] = data[40, 2]
86
+ response[:reference] = data[42, 10]
87
+
88
+ extra_data = data[53...-1].split("\034")
89
+ response[:order_number] = extra_data[0]
90
+ response[:authentication_indicator] = extra_data[1]
91
+ response[:authentication_disclosure] = extra_data[2]
92
+ response
93
+ end
94
+ end
95
+ end
96
+ end
97
+
@@ -36,6 +36,8 @@ module ActiveMerchant #:nodoc:
36
36
  :credit => 4
37
37
  }
38
38
 
39
+ SUCCESS_CODES = [ '00', '08', '11', '16', '77' ]
40
+
39
41
  def initialize(options = {})
40
42
  requires!(options, :login, :password)
41
43
  @options = options
@@ -109,7 +111,7 @@ module ActiveMerchant #:nodoc:
109
111
  end
110
112
 
111
113
  def success?(response)
112
- response[:response_code] == "00"
114
+ SUCCESS_CODES.include?(response[:response_code])
113
115
  end
114
116
 
115
117
  def authorization_from(response)
@@ -195,6 +195,8 @@ module ActiveMerchant #:nodoc:
195
195
  authorization = authorize(money, creditcard, options)
196
196
  if authorization.success?
197
197
  capture(money, authorization.authorization)
198
+ else
199
+ authorization
198
200
  end
199
201
  end
200
202
 
@@ -265,7 +265,7 @@ module ActiveMerchant #:nodoc:
265
265
  def store(creditcard, options = {})
266
266
  parameters = {
267
267
  :verify => options[:verify] || 'y',
268
- :billingid => options[:billingid] || nil,
268
+ :billingid => options[:billingid] || options[:billing_id] || nil,
269
269
  }
270
270
 
271
271
  add_creditcard(parameters, creditcard)
@@ -0,0 +1,304 @@
1
+ require 'base64'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class WirecardGateway < Gateway
6
+ # Test server location
7
+ TEST_URL = 'https://c3-test.wirecard.com/secure/ssl-gateway'
8
+
9
+ # Live server location
10
+ LIVE_URL = 'https://c3.wirecard.com/secure/ssl-gateway'
11
+
12
+ # The Namespaces are not really needed, because it just tells the System, that there's actually no namespace used.
13
+ # It's just specified here for completeness.
14
+ ENVELOPE_NAMESPACES = {
15
+ 'xmlns:xsi' => 'http://www.w3.org/1999/XMLSchema-instance',
16
+ 'xsi:noNamespaceSchemaLocation' => 'wirecard.xsd'
17
+ }
18
+
19
+ PERMITTED_TRANSACTIONS = %w[ AUTHORIZATION CAPTURE_AUTHORIZATION PURCHASE ]
20
+
21
+ RETURN_CODES = %w[ ACK NOK ]
22
+
23
+ # The countries the gateway supports merchants from as 2 digit ISO country codes
24
+ # TODO: Check supported countries
25
+ self.supported_countries = ['DE']
26
+
27
+ # Wirecard supports all major credit and debit cards:
28
+ # Visa, Mastercard, American Express, Diners Club,
29
+ # JCB, Switch, VISA Carte Bancaire, Visa Electron and UATP cards.
30
+ # They also support the latest anti-fraud systems such as Verified by Visa or Master Secure Code.
31
+ self.supported_cardtypes = [
32
+ :visa, :master, :american_express, :diners_club, :jcb, :switch
33
+ ]
34
+
35
+ # The homepage URL of the gateway
36
+ self.homepage_url = 'http://www.wirecard.com'
37
+
38
+ # The name of the gateway
39
+ self.display_name = 'Wirecard'
40
+
41
+ # The currency should normally be EUROs
42
+ self.default_currency = 'EUR'
43
+
44
+ # 100 is 1.00 Euro
45
+ self.money_format = :cents
46
+
47
+ def initialize(options = {})
48
+ # verify that username and password are supplied
49
+ requires!(options, :login, :password)
50
+ # unfortunately Wirecard also requires a BusinessCaseSignature in the XML request
51
+ requires!(options, :signature)
52
+ @options = options
53
+ super
54
+ end
55
+
56
+ # Should run against the test servers or not?
57
+ def test?
58
+ @options[:test] || super
59
+ end
60
+
61
+ # Authorization
62
+ def authorize(money, creditcard, options = {})
63
+ prepare_options_hash(options)
64
+ @options[:credit_card] = creditcard
65
+ request = build_request(:authorization, money, @options)
66
+ commit(request)
67
+ end
68
+
69
+
70
+ # Capture Authorization
71
+ def capture(money, authorization, options = {})
72
+ prepare_options_hash(options)
73
+ @options[:authorization] = authorization
74
+ request = build_request(:capture_authorization, money, @options)
75
+ commit(request)
76
+ end
77
+
78
+
79
+ # Purchase
80
+ def purchase(money, creditcard, options = {})
81
+ prepare_options_hash(options)
82
+ @options[:credit_card] = creditcard
83
+ request = build_request(:purchase, money, @options)
84
+ commit(request)
85
+ end
86
+
87
+ private
88
+
89
+ def prepare_options_hash(options)
90
+ @options.update(options)
91
+ setup_address_hash!(options)
92
+ end
93
+
94
+ # Create all address hash key value pairs so that
95
+ # it still works if only provided with one or two of them
96
+ def setup_address_hash!(options)
97
+ options[:billing_address] = options[:billing_address] || options[:address] || {}
98
+ options[:shipping_address] = options[:shipping_address] || {}
99
+ # Include Email in address-hash from options-hash
100
+ options[:billing_address][:email] = options[:email] if options[:email]
101
+ end
102
+
103
+ # Contact WireCard, make the XML request, and parse the
104
+ # reply into a Response object
105
+ def commit(request)
106
+ headers = { 'Content-Type' => 'text/xml',
107
+ 'Authorization' => encoded_credentials }
108
+
109
+ response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, request, headers))
110
+ # Pending Status also means Acknowledged (as stated in their specification)
111
+ success = response[:FunctionResult] == "ACK" || response[:FunctionResult] == "PENDING"
112
+ message = response[:Message]
113
+ authorization = (success && @options[:action] == :authorization) ? response[:GuWID] : nil
114
+
115
+ Response.new(success, message, response,
116
+ :test => test?,
117
+ :authorization => authorization,
118
+ :avs_result => { :code => response[:avsCode] },
119
+ :cvv_result => response[:cvCode]
120
+ )
121
+ end
122
+
123
+ # Generates the complete xml-message, that gets sent to the gateway
124
+ def build_request(action, money, options = {})
125
+ xml = Builder::XmlMarkup.new :indent => 2
126
+ xml.instruct!
127
+ xml.tag! 'WIRECARD_BXML' do
128
+ xml.tag! 'W_REQUEST' do
129
+ xml.tag! 'W_JOB' do
130
+ # TODO: OPTIONAL, check what value needs to be insert here
131
+ xml.tag! 'JobID', 'test dummy data'
132
+ # UserID for this transaction
133
+ xml.tag! 'BusinessCaseSignature', options[:signature] || options[:login]
134
+ # Create the whole rest of the message
135
+ add_transaction_data(xml, action, money, options)
136
+ end
137
+ end
138
+ end
139
+ xml.target!
140
+ end
141
+
142
+ # Includes the whole transaction data (payment, creditcard, address)
143
+ def add_transaction_data(xml, action, money, options = {})
144
+ options[:action] = action
145
+ # TODO: require order_id instead of auto-generating it if not supplied
146
+ options[:order_id] ||= generate_unique_id
147
+ transaction_type = action.to_s.upcase
148
+
149
+ xml.tag! "FNC_CC_#{transaction_type}" do
150
+ # TODO: OPTIONAL, check which param should be used here
151
+ xml.tag! 'FunctionID', options[:description] || 'Test dummy FunctionID'
152
+
153
+ xml.tag! 'CC_TRANSACTION' do
154
+ xml.tag! 'TransactionID', options[:order_id]
155
+ if [:authorization, :purchase].include?(action)
156
+ add_invoice(xml, money, options)
157
+ add_creditcard(xml, options[:credit_card])
158
+ add_address(xml, options[:billing_address])
159
+ elsif action == :capture_authorization
160
+ xml.tag! 'GuWID', options[:authorization] if options[:authorization]
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ # Includes the payment (amount, currency, country) to the transaction-xml
167
+ def add_invoice(xml, money, options)
168
+ xml.tag! 'Amount', amount(money)
169
+ xml.tag! 'Currency', options[:currency] || currency(money)
170
+ xml.tag! 'CountryCode', options[:billing_address][:country]
171
+ xml.tag! 'RECURRING_TRANSACTION' do
172
+ xml.tag! 'Type', options[:recurring] || 'Single'
173
+ end
174
+ end
175
+
176
+ # Includes the credit-card data to the transaction-xml
177
+ def add_creditcard(xml, creditcard)
178
+ raise "Creditcard must be supplied!" if creditcard.nil?
179
+ xml.tag! 'CREDIT_CARD_DATA' do
180
+ xml.tag! 'CreditCardNumber', creditcard.number
181
+ xml.tag! 'CVC2', creditcard.verification_value
182
+ xml.tag! 'ExpirationYear', creditcard.year
183
+ xml.tag! 'ExpirationMonth', format(creditcard.month, :two_digits)
184
+ xml.tag! 'CardHolderName', [creditcard.first_name, creditcard.last_name].join(' ')
185
+ end
186
+ end
187
+
188
+ # Includes the IP address of the customer to the transaction-xml
189
+ def add_customer_data(xml, options)
190
+ return unless options[:ip]
191
+ xml.tag! 'CONTACT_DATA' do
192
+ xml.tag! 'IPAddress', options[:ip]
193
+ end
194
+ end
195
+
196
+ # Includes the address to the transaction-xml
197
+ def add_address(xml, address)
198
+ return if address.nil?
199
+ xml.tag! 'CORPTRUSTCENTER_DATA' do
200
+ xml.tag! 'ADDRESS' do
201
+ xml.tag! 'Address1', address[:address1]
202
+ xml.tag! 'Address2', address[:address2] if address[:address2]
203
+ xml.tag! 'City', address[:city]
204
+ xml.tag! 'ZipCode', address[:zip]
205
+ xml.tag! 'State', address[:state].blank? ? 'N/A' : address[:state]
206
+ xml.tag! 'Country', address[:country]
207
+ xml.tag! 'Phone', address[:phone]
208
+ xml.tag! 'Email', address[:email]
209
+ end
210
+ end
211
+ end
212
+
213
+
214
+ # Read the XML message from the gateway and check if it was successful,
215
+ # and also extract required return values from the response.
216
+ def parse(xml)
217
+ basepath = '/WIRECARD_BXML/W_RESPONSE'
218
+ response = {}
219
+
220
+ xml = REXML::Document.new(xml)
221
+ if root = REXML::XPath.first(xml, "#{basepath}/W_JOB")
222
+ parse_response(response, root)
223
+ elsif root = REXML::XPath.first(xml, "/#{basepath}/ERROR")
224
+ parse_error(response, root)
225
+ else
226
+ response[:Message] = "No valid XML response message received. \
227
+ Propably wrong credentials supplied with HTTP header."
228
+ end
229
+
230
+ response
231
+ end
232
+
233
+ # Parse the <ProcessingStatus> Element which containts all important information
234
+ def parse_response(response, root)
235
+ status = nil
236
+ # get the root element for this Transaction
237
+ root.elements.to_a.each do |node|
238
+ if node.name =~ /FNC_CC_/
239
+ status = REXML::XPath.first(node, "CC_TRANSACTION/PROCESSING_STATUS")
240
+ end
241
+ end
242
+ # Get message
243
+ message = ''
244
+ if info = status.elements['Info']
245
+ message << info.text
246
+ end
247
+ # Get errors if available and append them to the message
248
+ errors = errors_to_string(status)
249
+ unless errors.strip.blank?
250
+ message << ' - ' unless message.strip.blank?
251
+ message << errors
252
+ end
253
+ response[:Message] = message
254
+
255
+ # Get basic response information
256
+ status.elements.to_a.each do |node|
257
+ response[node.name.to_sym] = (node.text || '').strip
258
+ end
259
+ end
260
+
261
+ # Parse a generic error response from the gateway
262
+ def parse_error(response, root)
263
+ # TODO: Implement parsing of more generic error messages
264
+ end
265
+
266
+ # Parses all <ERROR> elements in the response and converts the information
267
+ # to a single string
268
+ def errors_to_string(status)
269
+ # Get context error messages (can be 0..*)
270
+ errors = []
271
+ REXML::XPath.each(status, "ERROR") do |error_elem|
272
+ error = {}
273
+ error[:Advice] = []
274
+ error[:Message] = error_elem.elements['Message'].text
275
+ error_elem.elements.each('Advice') do |advice|
276
+ error[:Advice] << advice.text
277
+ end
278
+ errors << error
279
+ end
280
+ # Convert all messages to a single string
281
+ string = ''
282
+ errors.each do |error|
283
+ string << error[:Message]
284
+ error[:Advice].each_with_index do |advice, index|
285
+ string << ' (' if index == 0
286
+ string << "#{index+1}. #{advice}"
287
+ string << ' and ' if index < error[:Advice].size - 1
288
+ string << ')' if index == error[:Advice].size - 1
289
+ end
290
+ end
291
+ string
292
+ end
293
+
294
+ # Encode login and password in Base64 to supply as HTTP header
295
+ # (for http basic authentication)
296
+ def encoded_credentials
297
+ credentials = [@options[:login], @options[:password]].join(':')
298
+ "Basic " << Base64.encode64(credentials).strip
299
+ end
300
+
301
+ end
302
+ end
303
+ end
304
+
@@ -10,6 +10,12 @@ require 'active_merchant/billing/integrations/two_checkout'
10
10
  require 'active_merchant/billing/integrations/hi_trust'
11
11
 
12
12
  # make the bogus gateway be classified correctly by the inflector
13
- Inflector.inflections do |inflect|
14
- inflect.uncountable 'bogus'
13
+ if defined?(ActiveSupport::Inflector)
14
+ ActiveSupport::Inflector.inflections do |inflect|
15
+ inflect.uncountable 'bogus'
16
+ end
17
+ else
18
+ Inflector.inflections do |inflect|
19
+ inflect.uncountable 'bogus'
20
+ end
15
21
  end