activemerchant 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/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