activemerchant 1.0.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 (55) hide show
  1. data/CHANGELOG +40 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +93 -0
  4. data/lib/active_merchant.rb +59 -0
  5. data/lib/active_merchant/billing/base.rb +52 -0
  6. data/lib/active_merchant/billing/credit_card.rb +217 -0
  7. data/lib/active_merchant/billing/gateway.rb +100 -0
  8. data/lib/active_merchant/billing/gateways.rb +15 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +236 -0
  10. data/lib/active_merchant/billing/gateways/bogus.rb +92 -0
  11. data/lib/active_merchant/billing/gateways/eway.rb +235 -0
  12. data/lib/active_merchant/billing/gateways/linkpoint.rb +445 -0
  13. data/lib/active_merchant/billing/gateways/moneris.rb +205 -0
  14. data/lib/active_merchant/billing/gateways/payflow.rb +84 -0
  15. data/lib/active_merchant/billing/gateways/payflow/f73e89fd.0 +17 -0
  16. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +190 -0
  17. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +30 -0
  18. data/lib/active_merchant/billing/gateways/payflow_express.rb +123 -0
  19. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +9 -0
  20. data/lib/active_merchant/billing/gateways/payflow_uk.rb +17 -0
  21. data/lib/active_merchant/billing/gateways/paypal.rb +90 -0
  22. data/lib/active_merchant/billing/gateways/paypal/api_cert_chain.crt +35 -0
  23. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +208 -0
  24. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +30 -0
  25. data/lib/active_merchant/billing/gateways/paypal_express.rb +115 -0
  26. data/lib/active_merchant/billing/gateways/psigate.rb +265 -0
  27. data/lib/active_merchant/billing/gateways/trust_commerce.rb +330 -0
  28. data/lib/active_merchant/billing/gateways/usa_epay.rb +189 -0
  29. data/lib/active_merchant/billing/integrations.rb +12 -0
  30. data/lib/active_merchant/billing/integrations/action_view_helper.rb +65 -0
  31. data/lib/active_merchant/billing/integrations/bogus.rb +17 -0
  32. data/lib/active_merchant/billing/integrations/bogus/helper.rb +17 -0
  33. data/lib/active_merchant/billing/integrations/bogus/notification.rb +11 -0
  34. data/lib/active_merchant/billing/integrations/chronopay.rb +17 -0
  35. data/lib/active_merchant/billing/integrations/chronopay/helper.rb +81 -0
  36. data/lib/active_merchant/billing/integrations/chronopay/notification.rb +156 -0
  37. data/lib/active_merchant/billing/integrations/gestpay.rb +21 -0
  38. data/lib/active_merchant/billing/integrations/gestpay/common.rb +42 -0
  39. data/lib/active_merchant/billing/integrations/gestpay/helper.rb +72 -0
  40. data/lib/active_merchant/billing/integrations/gestpay/notification.rb +83 -0
  41. data/lib/active_merchant/billing/integrations/helper.rb +79 -0
  42. data/lib/active_merchant/billing/integrations/nochex.rb +21 -0
  43. data/lib/active_merchant/billing/integrations/nochex/helper.rb +68 -0
  44. data/lib/active_merchant/billing/integrations/nochex/notification.rb +101 -0
  45. data/lib/active_merchant/billing/integrations/notification.rb +52 -0
  46. data/lib/active_merchant/billing/integrations/paypal.rb +35 -0
  47. data/lib/active_merchant/billing/integrations/paypal/helper.rb +103 -0
  48. data/lib/active_merchant/billing/integrations/paypal/notification.rb +187 -0
  49. data/lib/active_merchant/billing/response.rb +28 -0
  50. data/lib/active_merchant/lib/country.rb +297 -0
  51. data/lib/active_merchant/lib/posts_data.rb +21 -0
  52. data/lib/active_merchant/lib/requires_parameters.rb +17 -0
  53. data/lib/active_merchant/lib/validateable.rb +76 -0
  54. data/lib/tasks/cia.rb +90 -0
  55. metadata +129 -0
@@ -0,0 +1,100 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'active_merchant/billing/response'
4
+
5
+ module ActiveMerchant #:nodoc:
6
+ module Billing #:nodoc:
7
+ # The Gateway class is the base class for all ActiveMerchant gateway
8
+ # implementations. The list of gateway functions that concrete
9
+ # gateway classes can and should implement include the following:
10
+ #
11
+ # === Core operations supported by most gateways
12
+ # * purchase(money, creditcard, options = {})
13
+ # * authorize(money, creditcard, options = {})
14
+ # * capture(money, authorization, options = {})
15
+ # * void(identification, options = {})
16
+ # * credit(money, identification, options = {})
17
+ class Gateway
18
+ include PostsData
19
+ include RequiresParameters
20
+
21
+ # The format of the amounts used by the gateway
22
+ # :dollars => '12.50'
23
+ # :cents => '1250'
24
+ class_inheritable_accessor :money_format
25
+ self.money_format = :dollars
26
+
27
+ # Return the matching gateway for the provider
28
+ # * <tt>bogus</tt>: BogusGateway - Does nothing ( for testing)
29
+ # * <tt>moneris</tt>: MonerisGateway
30
+ # * <tt>authorize_net</tt>: AuthorizeNetGateway
31
+ # * <tt>trust_commerce</tt>: TrustCommerceGateway
32
+ #
33
+ # ActiveMerchant::Base.gateway('moneris').new
34
+ def self.gateway(name)
35
+ ActiveMerchant::Billing.const_get("#{name.to_s.downcase}_gateway".camelize)
36
+ end
37
+
38
+ # Does this gateway support credit cards of the passed type?
39
+ def self.supports?(type)
40
+ supported_cardtypes.include?(type.intern)
41
+ end
42
+
43
+ # Get a list of supported credit card types for this gateway
44
+ def self.supported_cardtypes
45
+ []
46
+ end
47
+
48
+ attr_reader :options
49
+ # Initialize a new gateway
50
+ #
51
+ # See the documentation for the gateway you will be using to make sure there
52
+ # are no other required options
53
+ def initialize(options = {})
54
+ @ssl_strict = options[:ssl_strict] || false
55
+ end
56
+
57
+ # Are we running in test mode?
58
+ def test?
59
+ Base.gateway_mode == :test
60
+ end
61
+
62
+ private
63
+ def name
64
+ self.class.name.scan(/\:\:(\w+)Gateway/).flatten.first
65
+ end
66
+
67
+ def test_result_from_cc_number(number)
68
+ return false unless test?
69
+
70
+ case number.to_s
71
+ when '1', 'success'
72
+ Response.new(true, 'Successful test mode response', {:receiptid => '#0001'}, :test => true, :authorization => '5555')
73
+ when '2', 'failure'
74
+ Response.new(false, 'Failed test mode response', {:receiptid => '#0001'}, :test => true)
75
+ when '3', 'error'
76
+ raise Error, 'big bad exception'
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ # Return a string with the amount in the appropriate format
83
+ def amount(money)
84
+ return nil if money.nil?
85
+ cents = money.respond_to?(:cents) ? money.cents : money
86
+
87
+ if money.is_a?(String) or cents.to_i < 0
88
+ raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
89
+ end
90
+
91
+ case self.money_format
92
+ when :cents
93
+ cents.to_s
94
+ else
95
+ sprintf("%.2f", cents.to_f/100)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_merchant/billing/gateway'
2
+ require 'active_merchant/billing/gateways/bogus'
3
+ require 'active_merchant/billing/gateways/psigate'
4
+ require 'active_merchant/billing/gateways/authorize_net'
5
+ require 'active_merchant/billing/gateways/moneris'
6
+ require 'active_merchant/billing/gateways/trust_commerce'
7
+ require 'active_merchant/billing/gateways/linkpoint'
8
+ require 'active_merchant/billing/gateways/paypal'
9
+ require 'active_merchant/billing/gateways/paypal_express'
10
+ require 'active_merchant/billing/gateways/eway'
11
+ require 'active_merchant/billing/gateways/usa_epay'
12
+ require 'active_merchant/billing/gateways/payflow'
13
+ require 'active_merchant/billing/gateways/payflow_express'
14
+ require 'active_merchant/billing/gateways/payflow_uk'
15
+ require 'active_merchant/billing/gateways/payflow_express_uk'
@@ -0,0 +1,236 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+
4
+ class AuthorizeNetGateway < Gateway
5
+ API_VERSION = '3.1'
6
+ LIVE_URL = "https://secure.authorize.net/gateway/transact.dll"
7
+ TEST_URL = "https://test.authorize.net/gateway/transact.dll"
8
+
9
+ APPROVED, DECLINED, ERROR = 1, 2, 3
10
+
11
+ RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT = 0, 2, 3
12
+ AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38
13
+
14
+ CARD_CODE_ERRORS = %w( N S )
15
+
16
+ CARD_CODE_MESSAGES = {
17
+ "M" => "Card verification number matched",
18
+ "N" => "Card verification number didn't match",
19
+ "P" => "Card verification number was not processed",
20
+ "S" => "Card verification number should be on card but was not indicated",
21
+ "U" => "Issuer was not certified for card verification"
22
+ }
23
+
24
+ AVS_ERRORS = %w( A E N R W Z )
25
+
26
+ AVS_MESSAGES = {
27
+ "A" => "Street address matches billing information, zip/postal code does not",
28
+ "B" => "Address information not provided for address verification check",
29
+ "E" => "Address verification service error",
30
+ "G" => "Non-U.S. card-issuing bank",
31
+ "N" => "Neither street address nor zip/postal match billing information",
32
+ "P" => "Address verification not applicable for this transaction",
33
+ "R" => "Payment gateway was unavailable or timed out",
34
+ "S" => "Address verification service not supported by issuer",
35
+ "U" => "Address information is unavailable",
36
+ "W" => "9-digit zip/postal code matches billing information, street address does not",
37
+ "X" => "Street address and 9-digit zip/postal code matches billing information",
38
+ "Y" => "Street address and 5-digit zip/postal code matches billing information",
39
+ "Z" => "5-digit zip/postal code matches billing information, street address does not",
40
+ }
41
+
42
+ # URL
43
+ attr_reader :url
44
+ attr_reader :response
45
+ attr_reader :options
46
+
47
+ def initialize(options = {})
48
+ requires!(options, :login, :password)
49
+ @options = options
50
+ super
51
+ end
52
+
53
+ def authorize(money, creditcard, options = {})
54
+ post = {}
55
+ add_invoice(post, options)
56
+ add_creditcard(post, creditcard)
57
+ add_address(post, options)
58
+ add_customer_data(post, options)
59
+
60
+ commit('AUTH_ONLY', money, post)
61
+ end
62
+
63
+ def purchase(money, creditcard, options = {})
64
+ post = {}
65
+ add_invoice(post, options)
66
+ add_creditcard(post, creditcard)
67
+ add_address(post, options)
68
+ add_customer_data(post, options)
69
+
70
+ commit('AUTH_CAPTURE', money, post)
71
+ end
72
+
73
+ def capture(money, authorization, options = {})
74
+ post = {:trans_id => authorization}
75
+ add_customer_data(post, options)
76
+ commit('PRIOR_AUTH_CAPTURE', money, post)
77
+ end
78
+
79
+ def void(authorization, options = {})
80
+ post = {:trans_id => authorization}
81
+ commit('VOID', nil, post)
82
+ end
83
+
84
+ # We support visa and master card
85
+ def self.supported_cardtypes
86
+ [:visa, :master, :american_express, :discover]
87
+ end
88
+
89
+ private
90
+
91
+ def expdate(creditcard)
92
+ year = sprintf("%.4i", creditcard.year)
93
+ month = sprintf("%.2i", creditcard.month)
94
+
95
+ "#{year[-2..-1]}#{month}"
96
+ end
97
+
98
+ def commit(action, money, parameters)
99
+ parameters[:amount] = amount(money) unless action == 'VOID'
100
+
101
+ # Only activate the test_request when the :test option is passed in
102
+ parameters[:test_request] = @options[:test] ? 'TRUE' : 'FALSE'
103
+
104
+ if result = test_result_from_cc_number(parameters[:card_num])
105
+ return result
106
+ end
107
+
108
+ url = test? ? TEST_URL : LIVE_URL
109
+ data = ssl_post url, post_data(action, parameters)
110
+
111
+ @response = parse(data)
112
+ success = @response[:response_code] == APPROVED
113
+ message = message_from(@response)
114
+
115
+ # Return the response. The authorization can be taken out of the transaction_id
116
+ # Test Mode on/off is something we have to parse from the response text.
117
+ # It usually looks something like this
118
+ #
119
+ # (TESTMODE) Successful Sale
120
+ #
121
+
122
+ test_mode = test? || message =~ /TESTMODE/
123
+
124
+ Response.new(success, message, @response,
125
+ :test => test_mode,
126
+ :authorization => @response[:transaction_id]
127
+ )
128
+ end
129
+
130
+ def parse(body)
131
+ fields = body[1..-2].split(/\$,\$/)
132
+
133
+ results = {
134
+ :response_code => fields[RESPONSE_CODE].to_i,
135
+ :response_reason_code => fields[RESPONSE_REASON_CODE],
136
+ :response_reason_text => fields[RESPONSE_REASON_TEXT],
137
+ :avs_result_code => fields[AVS_RESULT_CODE],
138
+ :transaction_id => fields[TRANSACTION_ID],
139
+ :card_code => fields[CARD_CODE_RESPONSE_CODE]
140
+ }
141
+
142
+
143
+ results[:card_code_message] = CARD_CODE_MESSAGES[results[:card_code]] if results[:card_code]
144
+ results[:avs_message] = AVS_MESSAGES[results[:avs_result_code]] if results[:avs_result_code]
145
+
146
+ results
147
+ end
148
+
149
+ def post_data(action, parameters = {})
150
+ post = {}
151
+
152
+ post[:version] = API_VERSION
153
+ post[:login] = @options[:login]
154
+ post[:tran_key] = @options[:password]
155
+ post[:relay_response] = "FALSE"
156
+ post[:type] = action
157
+ post[:delim_data] = "TRUE"
158
+ post[:delim_char] = ","
159
+ post[:encap_char] = "$"
160
+
161
+ request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
162
+ request
163
+ end
164
+
165
+ def add_invoice(post, options)
166
+ post[:invoice_num] = options[:order_id]
167
+ post[:description] = options[:description]
168
+ end
169
+
170
+ def add_creditcard(post, creditcard)
171
+ post[:card_num] = creditcard.number
172
+ post[:card_code] = creditcard.verification_value if creditcard.verification_value?
173
+ post[:exp_date] = expdate(creditcard)
174
+ post[:first_name] = creditcard.first_name
175
+ post[:last_name] = creditcard.last_name
176
+ end
177
+
178
+ def add_customer_data(post, options)
179
+ if options.has_key? :email
180
+ post[:email] = options[:email]
181
+ post[:email_customer] = false
182
+ end
183
+
184
+ if options.has_key? :customer
185
+ post[:cust_id] = options[:customer]
186
+ end
187
+
188
+ if options.has_key? :ip
189
+ post[:customer_ip] = options[:ip]
190
+ end
191
+ end
192
+
193
+ def add_address(post, options)
194
+
195
+ if address = options[:billing_address] || options[:address]
196
+ post[:address] = address[:address1].to_s
197
+ post[:company] = address[:company].to_s
198
+ post[:phone] = address[:phone].to_s
199
+ post[:zip] = address[:zip].to_s
200
+ post[:city] = address[:city].to_s
201
+ post[:country] = address[:country].to_s
202
+ post[:state] = address[:state].blank? ? 'n/a' : address[:state]
203
+ end
204
+ end
205
+
206
+ # Make a ruby type out of the response string
207
+ def normalize(field)
208
+ case field
209
+ when "true" then true
210
+ when "false" then false
211
+ when "" then nil
212
+ when "null" then nil
213
+ else field
214
+ end
215
+ end
216
+
217
+ def message_from(results)
218
+ if results[:response_code] == DECLINED
219
+ return CARD_CODE_MESSAGES[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code])
220
+ return AVS_MESSAGES[results[:avs_result_code]] if AVS_ERRORS.include?(results[:avs_result_code])
221
+ end
222
+
223
+ return results[:response_reason_text][0..-2] # Forget the punctuation at the end
224
+ end
225
+
226
+ def expdate(creditcard)
227
+ year = sprintf("%.4i", creditcard.year)
228
+ month = sprintf("%.2i", creditcard.month)
229
+
230
+ "#{month}#{year[-2..-1]}"
231
+ end
232
+ end
233
+
234
+ AuthorizedNetGateway = AuthorizeNetGateway
235
+ end
236
+ end
@@ -0,0 +1,92 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ # Bogus Gateway
4
+ class BogusGateway < Gateway
5
+
6
+ def authorize(money, creditcard, options = {})
7
+ case creditcard.number
8
+ when '1'
9
+ Response.new(true, "Bogus Gateway: Forced success", {:authorized_amount => money.to_s}, :test => true, :authorization => '53433' )
10
+ when '2'
11
+ Response.new(false, "Bogus Gateway: Forced failure", {:authorized_amount => money.to_s, :error => 'Bogus Gateway: Forced failure' }, :test => true)
12
+ else
13
+ raise Error, 'Bogus Gateway: Use CreditCard number 1 for success, 2 for exception and anything else for error'
14
+ end
15
+ end
16
+
17
+ def purchase(money, creditcard, options = {})
18
+ case creditcard.number
19
+ when '1'
20
+ Response.new(true, "Bogus Gateway: Forced success", {:paid_amount => money.to_s}, :test => true)
21
+ when '2'
22
+ Response.new(false, "Bogus Gateway: Forced failure", {:paid_amount => money.to_s, :error => 'Bogus Gateway: Forced failure' },:test => true)
23
+ else
24
+ raise Error, 'Bogus Gateway: Use CreditCard number 1 for success, 2 for exception and anything else for error'
25
+ end
26
+ end
27
+
28
+ def credit(money, ident, options = {})
29
+ case ident
30
+ when '1'
31
+ Response.new(true, "Bogus Gateway: Forced success", {:paid_amount => money.to_s}, :test => true)
32
+ when '2'
33
+ Response.new(false, "Bogus Gateway: Forced failure", {:paid_amount => money.to_s, :error => 'Bogus Gateway: Forced failure' },:test => true)
34
+ else
35
+ raise Error, 'Bogus Gateway: Use trans_id 1 for success, 2 for exception and anything else for error'
36
+ end
37
+ end
38
+
39
+ def capture(money, ident, options = {})
40
+ case ident
41
+ when '1'
42
+ raise Error, 'Bogus Gateway: Use authorization number 1 for exception, 2 for error and anything else for success'
43
+ when '2'
44
+ Response.new(false, "Bogus Gateway: Forced failure", {:paid_amount => money.to_s, :error => 'Bogus Gateway: Forced failure' }, :test => true)
45
+ else
46
+ Response.new(true, "Bogus Gateway: Forced success", {:paid_amount => money.to_s}, :test => true)
47
+ end
48
+ end
49
+
50
+ def store(creditcard, options = {})
51
+ case creditcard.number
52
+ when '1'
53
+ Response.new(true, "Bogus Gateway: Forced success", {:billingid => '1'}, :test => true, :authorization => '53433' )
54
+ when '2'
55
+ Response.new(false, "Bogus Gateway: Forced failure", {:billingid => nil, :error => 'Bogus Gateway: Forced failure' }, :test => true)
56
+ else
57
+ raise Error, 'Bogus Gateway: Use CreditCard number 1 for success, 2 for exception and anything else for error'
58
+ end
59
+ end
60
+
61
+ def unstore(identification, options = {})
62
+ case identification
63
+ when '1'
64
+ Response.new(true, "Bogus Gateway: Forced success", {}, :test => true)
65
+ when '2'
66
+ Response.new(false, "Bogus Gateway: Forced failure", {:error => 'Bogus Gateway: Forced failure' },:test => true)
67
+ else
68
+ raise Error, 'Bogus Gateway: Use trans_id 1 for success, 2 for exception and anything else for error'
69
+ end
70
+ end
71
+
72
+ # We support visa and master card
73
+ def self.supported_cardtypes
74
+ [:bogus]
75
+ end
76
+
77
+ private
78
+
79
+ def deal_with_cc(creditcard)
80
+ case creditcard.number
81
+ when '1'
82
+ Response.new(true, "Bogus Gateway: Forced success", {}, :test => true)
83
+ when '2'
84
+ Response.new(false, "Bogus Gateway: Forced failure", @response, :test => true)
85
+ else
86
+ raise Error, 'Bogus Gateway: Use CreditCard number 1 for success, 2 for exception and anything else for error'
87
+ end
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,235 @@
1
+ # Author:: Lucas Carlson (mailto:lucas@rufy.com)
2
+ # Copyright:: Copyright (c) 2005 Lucas Carlson
3
+ # License:: Distributes under the same terms as Ruby
4
+
5
+ require 'rexml/document'
6
+
7
+ module ActiveMerchant #:nodoc:
8
+ module Billing #:nodoc:
9
+ # TO USE:
10
+ # First, make sure you have everything setup correctly and all of your dependencies in place with:
11
+ #
12
+ # require 'rubygems'
13
+ # require 'money'
14
+ # require 'active_merchant'
15
+ #
16
+ # The second line is a require for the 'money' library. Make sure you have it installed with 'gem install money'
17
+ #
18
+ # Using the money library, create a money object. Pass the dollar value in cents. In this case, $10 US becomes 1000.
19
+ #
20
+ # tendollar = Money.us_dollar(1000)
21
+ #
22
+ # Next, create a credit card object using a TC approved test card.
23
+ #
24
+ # creditcard = ActiveMerchant::Billing::CreditCard.new({
25
+ # :number => '4111111111111111',
26
+ # :month => 8,
27
+ # :year => 2006,
28
+ # :first_name => 'Longbob',
29
+ # :last_name => 'Longsen'
30
+ # })
31
+ # options = {
32
+ # :login => '87654321',
33
+ # :order_id => '1230123',
34
+ # :email => 'bob@testbob.com',
35
+ # :address => { :address1 => '47 Bobway, Bobville, WA, Australia',
36
+ # :zip => '2000'
37
+ # }
38
+ # :description => 'purchased items'
39
+ # }
40
+ #
41
+ # To finish setting up, create the active_merchant object you will be using, with the eWay gateway. If you have a
42
+ # functional eWay account, replace :login with your account info.
43
+ #
44
+ # gateway = ActiveMerchant::Billing::Base.gateway(:eway).new()
45
+ #
46
+ # Now we are ready to process our transaction
47
+ #
48
+ # response = gateway.purchase(tendollar, creditcard, options)
49
+ #
50
+ # Sending a transaction to TrustCommerce with active_merchant returns a Response object, which consistently allows you to:
51
+ #
52
+ # 1) Check whether the transaction was successful
53
+ #
54
+ # response.success?
55
+ #
56
+ # 2) Retrieve any message returned by eWay, either a "transaction was successful" note or an explanation of why the
57
+ # transaction was rejected.
58
+ #
59
+ # response.message
60
+ #
61
+ # 3) Retrieve and store the unique transaction ID returned by eWway, for use in referencing the transaction in the future.
62
+ #
63
+ # response.authorization
64
+ #
65
+ # This should be enough to get you started with eWay and active_merchant. For further information, review the methods
66
+ # below and the rest of active_merchant's documentation.
67
+
68
+ class EwayGateway < Gateway
69
+ TEST_URL = 'https://www.eway.com.au/gateway/xmltest/testpage.asp'
70
+ LIVE_URL = 'https://www.eway.com.au/gateway/xmlpayment.asp'
71
+
72
+ TEST_CVN_URL = 'https://www.eway.com.au/gateway_cvn/xmltest/testpage.asp'
73
+ LIVE_CVN_URL = 'https://www.eway.com.au/gateway_cvn/xmlpayment.asp'
74
+
75
+ MESSAGES = {
76
+ "00" => "Transaction was successfully processed",
77
+ "A8" => "Amount is invalid",
78
+ "A9" => "Card number is invalid",
79
+ "AA" => "Account is invalid",
80
+ "AB" => "Card expiry date is invalid",
81
+ "01" => "Card verification number didn't match",
82
+ "05" => "Card verification number didn't match"
83
+ }
84
+
85
+ attr_reader :url
86
+ attr_reader :response
87
+ attr_reader :options
88
+
89
+ self.money_format = :cents
90
+
91
+ def initialize(options = {})
92
+ requires!(options, :login)
93
+ @options = options
94
+ super
95
+ end
96
+
97
+ # ewayCustomerEmail, ewayCustomerAddress, ewayCustomerPostcode
98
+ def purchase(money, creditcard, options = {})
99
+ requires!(options, :order_id)
100
+
101
+ post = {}
102
+ add_creditcard(post, creditcard)
103
+ add_address(post, options)
104
+ add_customer_data(post, options)
105
+ add_invoice_data(post, options)
106
+ # The request fails if all of the fields aren't present
107
+ add_optional_data(post)
108
+
109
+ commit(money, post)
110
+ end
111
+
112
+ def self.supported_cardtypes
113
+ [:visa, :master]
114
+ end
115
+
116
+ private
117
+ def add_creditcard(post, creditcard)
118
+ post[:CardNumber] = creditcard.number
119
+ post[:CardExpiryMonth] = sprintf("%.2i", creditcard.month)
120
+ post[:CardExpiryYear] = sprintf("%.4i", creditcard.year)[-2..-1]
121
+ post[:CustomerFirstName] = creditcard.first_name
122
+ post[:CustomerLastName] = creditcard.last_name
123
+ post[:CardHoldersName] = creditcard.name
124
+
125
+ post[:CVN] = creditcard.verification_value if creditcard.verification_value?
126
+ end
127
+
128
+ def add_address(post, options)
129
+ if address = options[:billing_address] || options[:address]
130
+ post[:CustomerAddress] = address[:address1]
131
+ post[:CustomerPostcode] = address[:zip]
132
+ end
133
+ end
134
+
135
+ def add_customer_data(post, options)
136
+ post[:CustomerEmail] = options[:email]
137
+ end
138
+
139
+ def add_invoice_data(post, options)
140
+ post[:CustomerInvoiceRef] = options[:order_id]
141
+ post[:CustomerInvoiceDescription] = options[:description]
142
+ end
143
+
144
+ def add_optional_data(post)
145
+ post[:TrxnNumber] = nil
146
+ post[:Option1] = nil
147
+ post[:Option2] = nil
148
+ post[:Option3] = nil
149
+ end
150
+
151
+ def commit(money, parameters)
152
+
153
+ parameters[:TotalAmount] = amount(money)
154
+
155
+ if result = test_result_from_cc_number(parameters[:CardNumber])
156
+ return result
157
+ end
158
+
159
+ data = ssl_post gateway_url(parameters[:CVN], test?), post_data(parameters)
160
+
161
+ @response = parse(data)
162
+
163
+ success = (response[:ewaytrxnstatus] == "True")
164
+ message = message_from(response[:ewaytrxnerror])
165
+
166
+ Response.new(success, message, @response,
167
+ :authorization => response[:ewayauthcode]
168
+ )
169
+ end
170
+
171
+ # Parse eway response xml into a convinient hash
172
+ def parse(xml)
173
+ # "<?xml version=\"1.0\"?>".
174
+ # <ewayResponse>
175
+ # <ewayTrxnError></ewayTrxnError>
176
+ # <ewayTrxnStatus>True</ewayTrxnStatus>
177
+ # <ewayTrxnNumber>10002</ewayTrxnNumber>
178
+ # <ewayTrxnOption1></ewayTrxnOption1>
179
+ # <ewayTrxnOption2></ewayTrxnOption2>
180
+ # <ewayTrxnOption3></ewayTrxnOption3>
181
+ # <ewayReturnAmount>10</ewayReturnAmount>
182
+ # <ewayAuthCode>123456</ewayAuthCode>
183
+ # <ewayTrxnReference>987654321</ewayTrxnReference>
184
+ # </ewayResponse>
185
+
186
+ response = {}
187
+
188
+ xml = REXML::Document.new(xml)
189
+ xml.elements.each('//ewayResponse/*') do |node|
190
+
191
+ response[node.name.downcase.to_sym] = normalize(node.text)
192
+
193
+ end unless xml.root.nil?
194
+
195
+ response
196
+ end
197
+
198
+ def post_data(parameters = {})
199
+ parameters[:CustomerID] = @options[:login]
200
+
201
+ xml = REXML::Document.new
202
+ root = xml.add_element("ewaygateway")
203
+
204
+ parameters.each do |key, value|
205
+ root.add_element("eway#{key}").text = value
206
+ end
207
+ xml.to_s
208
+ end
209
+
210
+ def message_from(message)
211
+ return '' if message.blank?
212
+ MESSAGES[message[0,2]] || message
213
+ end
214
+
215
+ # Make a ruby type out of the response string
216
+ def normalize(field)
217
+ case field
218
+ when "true" then true
219
+ when "false" then false
220
+ when "" then nil
221
+ when "null" then nil
222
+ else field
223
+ end
224
+ end
225
+
226
+ def gateway_url(cvn, test)
227
+ if cvn
228
+ test ? TEST_CVN_URL : LIVE_CVN_URL
229
+ else
230
+ test ? TEST_URL : LIVE_URL
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end