activemerchant 1.29.1 → 1.31.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 (53) hide show
  1. data/CHANGELOG +58 -0
  2. data/CONTRIBUTORS +27 -0
  3. data/README.md +45 -41
  4. data/lib/active_merchant/billing/check.rb +11 -11
  5. data/lib/active_merchant/billing/credit_card.rb +1 -1
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +8 -8
  7. data/lib/active_merchant/billing/gateway.rb +2 -2
  8. data/lib/active_merchant/billing/gateways/authorize_net.rb +9 -1
  9. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +15 -4
  10. data/lib/active_merchant/billing/gateways/balanced.rb +9 -3
  11. data/lib/active_merchant/billing/gateways/banwire.rb +15 -1
  12. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +6 -2
  13. data/lib/active_merchant/billing/gateways/beanstream.rb +26 -24
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +5 -2
  15. data/lib/active_merchant/billing/gateways/cyber_source.rb +55 -22
  16. data/lib/active_merchant/billing/gateways/evo_ca.rb +308 -0
  17. data/lib/active_merchant/billing/gateways/eway.rb +114 -171
  18. data/lib/active_merchant/billing/gateways/eway_managed.rb +52 -22
  19. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +232 -0
  20. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +13 -2
  21. data/lib/active_merchant/billing/gateways/litle.rb +50 -19
  22. data/lib/active_merchant/billing/gateways/merchant_ware.rb +44 -9
  23. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +190 -0
  24. data/lib/active_merchant/billing/gateways/moneris.rb +3 -5
  25. data/lib/active_merchant/billing/gateways/moneris_us.rb +1 -1
  26. data/lib/active_merchant/billing/gateways/nab_transact.rb +20 -3
  27. data/lib/active_merchant/billing/gateways/netbilling.rb +1 -0
  28. data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
  29. data/lib/active_merchant/billing/gateways/ogone.rb +6 -4
  30. data/lib/active_merchant/billing/gateways/optimal_payment.rb +18 -3
  31. data/lib/active_merchant/billing/gateways/orbital.rb +9 -5
  32. data/lib/active_merchant/billing/gateways/payment_express.rb +62 -1
  33. data/lib/active_merchant/billing/gateways/paymill.rb +161 -0
  34. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
  35. data/lib/active_merchant/billing/gateways/paypal_express.rb +17 -11
  36. data/lib/active_merchant/billing/gateways/pin.rb +157 -0
  37. data/lib/active_merchant/billing/gateways/qbms.rb +3 -2
  38. data/lib/active_merchant/billing/gateways/quickpay.rb +66 -28
  39. data/lib/active_merchant/billing/gateways/sage_pay.rb +6 -0
  40. data/lib/active_merchant/billing/gateways/smart_ps.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/spreedly_core.rb +235 -0
  42. data/lib/active_merchant/billing/gateways/stripe.rb +1 -0
  43. data/lib/active_merchant/billing/gateways/wirecard.rb +15 -9
  44. data/lib/active_merchant/billing/gateways/worldpay.rb +19 -5
  45. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +4 -1
  46. data/lib/active_merchant/billing/integrations/paypal/notification.rb +39 -31
  47. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +13 -10
  48. data/lib/active_merchant/billing/integrations/quickpay/notification.rb +14 -14
  49. data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +2 -2
  50. data/lib/active_merchant/version.rb +1 -1
  51. data.tar.gz.sig +0 -0
  52. metadata +32 -24
  53. metadata.gz.sig +0 -0
@@ -0,0 +1,232 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class FirstdataE4Gateway < Gateway
4
+ self.test_url = "https://api.demo.globalgatewaye4.firstdata.com/transaction"
5
+ self.live_url = "https://api.globalgatewaye4.firstdata.com/transaction"
6
+
7
+ TRANSACTIONS = {
8
+ :sale => "00",
9
+ :authorization => "01",
10
+ :capture => "32",
11
+ :void => "33",
12
+ :credit => "34"
13
+ }
14
+
15
+ POST_HEADERS = {
16
+ "Accepts" => "application/xml",
17
+ "Content-Type" => "application/xml"
18
+ }
19
+
20
+ SUCCESS = "true"
21
+
22
+ SENSITIVE_FIELDS = [:verification_str2, :expiry_date, :card_number]
23
+
24
+ self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover]
25
+ self.supported_countries = ["CA", "US"]
26
+ self.default_currency = "USD"
27
+ self.homepage_url = "http://www.firstdata.com"
28
+ self.display_name = "FirstData Global Gateway e4"
29
+
30
+ # Create a new FirstdataE4Gateway
31
+ #
32
+ # The gateway requires that a valid login and password be passed
33
+ # in the +options+ hash.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # * <tt>:login</tt> -- The EXACT ID. Also known as the Gateway ID.
38
+ # (Found in your administration terminal settings)
39
+ # * <tt>:password</tt> -- The terminal password (not your account password)
40
+ def initialize(options = {})
41
+ requires!(options, :login, :password)
42
+ @options = options
43
+
44
+ super
45
+ end
46
+
47
+ def authorize(money, credit_card, options = {})
48
+ commit(:authorization, build_sale_or_authorization_request(money, credit_card, options))
49
+ end
50
+
51
+ def purchase(money, credit_card, options = {})
52
+ commit(:sale, build_sale_or_authorization_request(money, credit_card, options))
53
+ end
54
+
55
+ def capture(money, authorization, options = {})
56
+ commit(:capture, build_capture_or_credit_request(money, authorization, options))
57
+ end
58
+
59
+ def void(authorization, options = {})
60
+ commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options))
61
+ end
62
+
63
+ def refund(money, authorization, options = {})
64
+ commit(:credit, build_capture_or_credit_request(money, authorization, options))
65
+ end
66
+
67
+ private
68
+
69
+ def build_request(action, body)
70
+ xml = Builder::XmlMarkup.new
71
+
72
+ xml.instruct!
73
+ xml.tag! "Transaction" do
74
+ add_credentials(xml)
75
+ add_transaction_type(xml, action)
76
+ xml << body
77
+ end
78
+
79
+ xml.target!
80
+ end
81
+
82
+ def build_sale_or_authorization_request(money, credit_card, options)
83
+ xml = Builder::XmlMarkup.new
84
+
85
+ add_amount(xml, money)
86
+ add_credit_card(xml, credit_card)
87
+ add_customer_data(xml, options)
88
+ add_invoice(xml, options)
89
+
90
+ xml.target!
91
+ end
92
+
93
+ def build_capture_or_credit_request(money, identification, options)
94
+ xml = Builder::XmlMarkup.new
95
+
96
+ add_identification(xml, identification)
97
+ add_amount(xml, money)
98
+ add_customer_data(xml, options)
99
+
100
+ xml.target!
101
+ end
102
+
103
+ def add_credentials(xml)
104
+ xml.tag! "ExactID", @options[:login]
105
+ xml.tag! "Password", @options[:password]
106
+ end
107
+
108
+ def add_transaction_type(xml, action)
109
+ xml.tag! "Transaction_Type", TRANSACTIONS[action]
110
+ end
111
+
112
+ def add_identification(xml, identification)
113
+ authorization_num, transaction_tag, _ = identification.split(";")
114
+
115
+ xml.tag! "Authorization_Num", authorization_num
116
+ xml.tag! "Transaction_Tag", transaction_tag
117
+ end
118
+
119
+ def add_amount(xml, money)
120
+ xml.tag! "DollarAmount", amount(money)
121
+ end
122
+
123
+ def add_credit_card(xml, credit_card)
124
+ xml.tag! "Card_Number", credit_card.number
125
+ xml.tag! "Expiry_Date", expdate(credit_card)
126
+ xml.tag! "CardHoldersName", credit_card.name
127
+ xml.tag! "CardType", credit_card.brand
128
+
129
+ if credit_card.verification_value?
130
+ xml.tag! "CVD_Presence_Ind", "1"
131
+ xml.tag! "VerificationStr2", credit_card.verification_value
132
+ end
133
+ end
134
+
135
+ def add_customer_data(xml, options)
136
+ xml.tag! "Customer_Ref", options[:customer] if options[:customer]
137
+ xml.tag! "Client_IP", options[:ip] if options[:ip]
138
+ xml.tag! "Client_Email", options[:email] if options[:email]
139
+ end
140
+
141
+ def add_address(xml, options)
142
+ if address = (options[:billing_address] || options[:address])
143
+ xml.tag! "ZipCode", address[:zip]
144
+ end
145
+ end
146
+
147
+ def add_invoice(xml, options)
148
+ xml.tag! "Reference_No", options[:order_id]
149
+ xml.tag! "Reference_3", options[:description] if options[:description]
150
+ end
151
+
152
+ def expdate(credit_card)
153
+ "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
154
+ end
155
+
156
+ def commit(action, request)
157
+ url = (test? ? self.test_url : self.live_url)
158
+ begin
159
+ response = parse(ssl_post(url, build_request(action, request), POST_HEADERS))
160
+ rescue ResponseError => e
161
+ response = parse_error(e.response)
162
+ end
163
+
164
+ Response.new(successful?(response), message_from(response), response,
165
+ :test => test?,
166
+ :authorization => authorization_from(response),
167
+ :avs_result => {:code => response[:avs]},
168
+ :cvv_result => response[:cvv2]
169
+ )
170
+ end
171
+
172
+ def successful?(response)
173
+ response[:transaction_approved] == SUCCESS
174
+ end
175
+
176
+ def authorization_from(response)
177
+ if response[:authorization_num] && response[:transaction_tag]
178
+ [
179
+ response[:authorization_num],
180
+ response[:transaction_tag],
181
+ (response[:dollar_amount].to_f * 100).to_i
182
+ ].join(";")
183
+ else
184
+ ""
185
+ end
186
+ end
187
+
188
+ def money_from_authorization(auth)
189
+ _, _, amount = auth.split(/;/, 3)
190
+ amount.to_i # return the # of cents, no need to divide
191
+ end
192
+
193
+ def message_from(response)
194
+ if(response[:faultcode] && response[:faultstring])
195
+ response[:faultstring]
196
+ elsif(response[:error_number] != "0")
197
+ response[:error_description]
198
+ else
199
+ result = (response[:exact_message] || "")
200
+ result << " - #{response[:bank_message]}" if response[:bank_message].present?
201
+ result
202
+ end
203
+ end
204
+
205
+ def parse_error(error)
206
+ {
207
+ :transaction_approved => "false",
208
+ :error_number => error.code,
209
+ :error_description => error.body
210
+ }
211
+ end
212
+
213
+ def parse(xml)
214
+ response = {}
215
+ xml = REXML::Document.new(xml)
216
+
217
+ if root = REXML::XPath.first(xml, "//TransactionResult")
218
+ parse_elements(response, root)
219
+ end
220
+
221
+ response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) }
222
+ end
223
+
224
+ def parse_elements(response, root)
225
+ root.elements.to_a.each do |node|
226
+ response[node.name.gsub(/EXact/, "Exact").underscore.to_sym] = (node.text || "").strip
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+
@@ -9,12 +9,23 @@ module ActiveMerchant #:nodoc:
9
9
  #
10
10
  # ActiveMerchant expects the amounts to be given as an Integer in cents. In this case, 10 EUR becomes 1000.
11
11
  #
12
+ # Create certificates for authentication:
13
+ #
14
+ # The PEM file expected should contain both the certificate and the generated PEM file.
15
+ # Some sample shell commands to generate the certificates:
16
+ #
17
+ # openssl genrsa -aes128 -out priv.pem -passout pass:[YOUR PASSWORD] 1024
18
+ # openssl req -x509 -new -key priv.pem -passin pass:[YOUR PASSWORD] -days 3000 -out cert.cer
19
+ # cat cert.cer priv.pem > ideal.pem
20
+ #
21
+ # Following the steps above, upload cert.cer to the ideal web interface and pass the path of ideal.pem to the :pem option.
22
+ #
12
23
  # Configure the gateway using your iDEAL bank account info and security settings:
13
24
  #
14
25
  # Create gateway:
15
26
  # gateway = ActiveMerchant::Billing::IdealRabobankGateway.new(
16
- # :login => '123456789', # merchant number
17
- # :pem => File.read(RAILS_ROOT + '/config/ideal.pem'), # put certificate and PEM in this file
27
+ # :login => '123456789', # 9 digit merchant number
28
+ # :pem => File.read(Rails.root + 'config/ideal.pem'),
18
29
  # :password => 'password' # password for the PEM key
19
30
  # )
20
31
  #
@@ -71,13 +71,13 @@ module ActiveMerchant #:nodoc:
71
71
  super
72
72
  end
73
73
 
74
- def authorize(money, creditcard, options = {})
75
- to_pass = create_credit_card_hash(money, creditcard, options)
74
+ def authorize(money, creditcard_or_token, options = {})
75
+ to_pass = build_authorize_request(money, creditcard_or_token, options)
76
76
  build_response(:authorization, @litle.authorization(to_pass))
77
77
  end
78
78
 
79
- def purchase(money, creditcard, options = {})
80
- to_pass = create_credit_card_hash(money, creditcard, options)
79
+ def purchase(money, creditcard_or_token, options = {})
80
+ to_pass = build_purchase_request(money, creditcard_or_token, options)
81
81
  build_response(:sale, @litle.sale(to_pass))
82
82
  end
83
83
 
@@ -98,7 +98,7 @@ module ActiveMerchant #:nodoc:
98
98
 
99
99
  def store(creditcard, options = {})
100
100
  to_pass = create_token_hash(creditcard, options)
101
- build_response(:registerToken, @litle.register_token_request(to_pass), %w(801 802))
101
+ build_response(:registerToken, @litle.register_token_request(to_pass), %w(000 801 802))
102
102
  end
103
103
 
104
104
  private
@@ -142,11 +142,17 @@ module ActiveMerchant #:nodoc:
142
142
  if response['response'] == "0"
143
143
  detail = response["#{kind}Response"]
144
144
  fraud = fraud_result(detail)
145
+ authorization = case kind
146
+ when :registerToken
147
+ response['registerTokenResponse']['litleToken']
148
+ else
149
+ detail['litleTxnId']
150
+ end
145
151
  Response.new(
146
152
  valid_responses.include?(detail['response']),
147
153
  detail['message'],
148
154
  {:litleOnlineResponse => response},
149
- :authorization => detail['litleTxnId'],
155
+ :authorization => authorization,
150
156
  :avs_result => {:code => fraud['avs']},
151
157
  :cvv_result => fraud['cvv'],
152
158
  :test => test?
@@ -156,27 +162,52 @@ module ActiveMerchant #:nodoc:
156
162
  end
157
163
  end
158
164
 
159
- def create_credit_card_hash(money, creditcard, options)
160
- cc_type = CARD_TYPE[creditcard.brand]
165
+ def build_authorize_request(money, creditcard_or_token, options)
166
+ hash = create_hash(money, options)
167
+
168
+ add_credit_card_or_token_hash(hash, creditcard_or_token)
169
+
170
+ hash
171
+ end
172
+
173
+ def build_purchase_request(money, creditcard_or_token, options)
174
+ hash = create_hash(money, options)
175
+
176
+ add_credit_card_or_token_hash(hash, creditcard_or_token)
161
177
 
162
- exp_date_yr = creditcard.year.to_s()[2..3]
178
+ hash
179
+ end
163
180
 
164
- if( creditcard.month.to_s().length == 1 )
165
- exp_date_mo = '0' + creditcard.month.to_s()
181
+ def add_credit_card_or_token_hash(hash, creditcard_or_token)
182
+ if creditcard_or_token.is_a?(String)
183
+ add_token_hash(hash, creditcard_or_token)
166
184
  else
167
- exp_date_mo = creditcard.month.to_s()
185
+ add_credit_card_hash(hash, creditcard_or_token)
168
186
  end
187
+ end
169
188
 
170
- exp_date = exp_date_mo + exp_date_yr
189
+ def add_token_hash(hash, creditcard_or_token)
190
+ token_info = {
191
+ 'litleToken' => creditcard_or_token
192
+ }
193
+
194
+ hash['token'] = token_info
195
+ hash
196
+ end
197
+
198
+ def add_credit_card_hash(hash, creditcard)
199
+ cc_type = CARD_TYPE[creditcard.brand]
200
+ exp_date_yr = creditcard.year.to_s[2..3]
201
+ exp_date_mo = '%02d' % creditcard.month.to_i
202
+ exp_date = exp_date_mo + exp_date_yr
171
203
 
172
204
  card_info = {
173
- 'type' => cc_type,
174
- 'number' => creditcard.number,
175
- 'expDate' => exp_date,
176
- 'cardValidationNum' => creditcard.verification_value
205
+ 'type' => cc_type,
206
+ 'number' => creditcard.number,
207
+ 'expDate' => exp_date,
208
+ 'cardValidationNum' => creditcard.verification_value
177
209
  }
178
210
 
179
- hash = create_hash(money, options)
180
211
  hash['card'] = card_info
181
212
  hash
182
213
  end
@@ -258,7 +289,7 @@ module ActiveMerchant #:nodoc:
258
289
  'customerId' => options[:customer],
259
290
  'reportGroup' => (options[:merchant] || @options[:merchant]),
260
291
  'merchantId' => (options[:merchant_id] || @options[:merchant_id]),
261
- 'orderSource' => 'ecommerce',
292
+ 'orderSource' => (options[:order_source] || 'ecommerce'),
262
293
  'enhancedData' => enhanced_data,
263
294
  'fraudCheckType' => fraud_check_type,
264
295
  'user' => (options[:user] || @options[:user]),
@@ -1,7 +1,10 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class MerchantWareGateway < Gateway
4
+ class_attribute :v4_live_url
5
+
4
6
  self.live_url = self.test_url = 'https://ps1.merchantware.net/MerchantWARE/ws/RetailTransaction/TXRetail.asmx'
7
+ self.v4_live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx'
5
8
 
6
9
  self.supported_countries = ['US']
7
10
  self.supported_cardtypes = [:visa, :master, :american_express, :discover]
@@ -12,13 +15,19 @@ module ActiveMerchant #:nodoc:
12
15
  "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
13
16
  "xmlns:env" => "http://schemas.xmlsoap.org/soap/envelope/"
14
17
  }
18
+ ENV_NAMESPACES_V4 = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
19
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
20
+ "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/"
21
+ }
22
+
15
23
  TX_NAMESPACE = "http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail"
24
+ TX_NAMESPACE_V4 = "http://schemas.merchantwarehouse.com/merchantware/40/Credit/"
16
25
 
17
26
  ACTIONS = {
18
27
  :purchase => "IssueKeyedSale",
19
28
  :authorize => "IssueKeyedPreAuth",
20
29
  :capture => "IssuePostAuth",
21
- :void => "IssueVoid",
30
+ :void => "VoidPreAuthorization",
22
31
  :credit => "IssueKeyedRefund",
23
32
  :reference_credit => "IssueRefundByReference"
24
33
  }
@@ -80,11 +89,10 @@ module ActiveMerchant #:nodoc:
80
89
  # * <tt>authorization</tt> - The authorization string returned from the initial authorization or purchase.
81
90
  def void(authorization, options = {})
82
91
  reference, options[:order_id] = split_reference(authorization)
83
-
84
- request = soap_request(:void) do |xml|
85
- add_reference(xml, reference)
92
+ request = v4_soap_request(:void) do |xml|
93
+ add_reference_token(xml, reference)
86
94
  end
87
- commit(:void, request)
95
+ commit(:void, request, true)
88
96
  end
89
97
 
90
98
  # Refund an amount back a cardholder
@@ -108,7 +116,6 @@ module ActiveMerchant #:nodoc:
108
116
  perform_reference_credit(money, reference, options)
109
117
  end
110
118
 
111
-
112
119
  private
113
120
 
114
121
  def soap_request(action)
@@ -125,6 +132,22 @@ module ActiveMerchant #:nodoc:
125
132
  xml.target!
126
133
  end
127
134
 
135
+ def v4_soap_request(action)
136
+ xml = Builder::XmlMarkup.new :indent => 2
137
+ xml.instruct!
138
+ xml.tag! "soap:Envelope", ENV_NAMESPACES_V4 do
139
+ xml.tag! "soap:Body" do
140
+ xml.tag! ACTIONS[:void], "xmlns" => TX_NAMESPACE_V4 do
141
+ xml.tag! "merchantName", @options[:name]
142
+ xml.tag! "merchantSiteId", @options[:login]
143
+ xml.tag! "merchantKey", @options[:password]
144
+ yield xml
145
+ end
146
+ end
147
+ end
148
+ xml.target!
149
+ end
150
+
128
151
  def build_purchase_request(action, money, credit_card, options)
129
152
  requires!(options, :order_id)
130
153
 
@@ -195,6 +218,10 @@ module ActiveMerchant #:nodoc:
195
218
  xml.tag! "strReferenceCode", reference
196
219
  end
197
220
 
221
+ def add_reference_token(xml, reference)
222
+ xml.tag! "token", reference
223
+ end
224
+
198
225
  def add_address(xml, options)
199
226
  if address = options[:billing_address] || options[:address]
200
227
  xml.tag! "strAVSStreetAddress", address[:address1]
@@ -258,11 +285,19 @@ module ActiveMerchant #:nodoc:
258
285
  response
259
286
  end
260
287
 
261
- def commit(action, request)
288
+ def soap_action(action, v4 = false)
289
+ v4 ? "#{TX_NAMESPACE_V4}#{ACTIONS[action]}" : "#{TX_NAMESPACE}/#{ACTIONS[action]}"
290
+ end
291
+
292
+ def url(v4 = false)
293
+ v4 ? v4_live_url : live_url
294
+ end
295
+
296
+ def commit(action, request, v4 = false)
262
297
  begin
263
- data = ssl_post(self.live_url, request,
298
+ data = ssl_post(url(v4), request,
264
299
  "Content-Type" => 'text/xml; charset=utf-8',
265
- "SOAPAction" => "http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail/#{ACTIONS[action]}"
300
+ "SOAPAction" => soap_action(action, v4)
266
301
  )
267
302
  response = parse(action, data)
268
303
  rescue ActiveMerchant::ResponseError => e
@@ -0,0 +1,190 @@
1
+ require 'digest/md5'
2
+ require 'rexml/document'
3
+
4
+ module ActiveMerchant #:nodoc:
5
+ module Billing #:nodoc:
6
+ class MerchantWarriorGateway < Gateway
7
+ TOKEN_TEST_URL = 'https://base.merchantwarrior.com/token/'
8
+ TOKEN_LIVE_URL = 'https://api.merchantwarrior.com/token/'
9
+
10
+ POST_TEST_URL = 'https://base.merchantwarrior.com/post/'
11
+ POST_LIVE_URL = 'https://api.merchantwarrior.com/post/'
12
+
13
+ self.supported_countries = ['AU']
14
+ self.supported_cardtypes = [:visa, :master, :american_express,
15
+ :diners_club, :discover]
16
+ self.homepage_url = 'http://www.merchantwarrior.com/'
17
+ self.display_name = 'MerchantWarrior'
18
+
19
+ self.money_format = :dollars
20
+ self.default_currency = 'AUD'
21
+
22
+ def initialize(options = {})
23
+ requires!(options, :merchant_uuid, :api_key, :api_passphrase)
24
+ super
25
+ end
26
+
27
+ def authorize(money, payment_method, options = {})
28
+ post = {}
29
+ add_amount(post, money, options)
30
+ add_product(post, options)
31
+ add_address(post, options)
32
+ add_payment_method(post, payment_method)
33
+ commit('processAuth', post)
34
+ end
35
+
36
+ def purchase(money, payment_method, options = {})
37
+ post = {}
38
+ add_amount(post, money, options)
39
+ add_product(post, options)
40
+ add_address(post, options)
41
+ add_payment_method(post, payment_method)
42
+ commit('processCard', post)
43
+ end
44
+
45
+ def capture(money, identification)
46
+ post = {}
47
+ add_amount(post, money, options)
48
+ add_transaction(post, identification)
49
+ post.merge!('captureAmount' => money.to_s)
50
+ commit('processCapture', post)
51
+ end
52
+
53
+ def refund(money, identification)
54
+ post = {}
55
+ add_amount(post, money, options)
56
+ add_transaction(post, identification)
57
+ post['refundAmount'] = money
58
+ commit('refundCard', post)
59
+ end
60
+
61
+ def store(creditcard, options = {})
62
+ post = {
63
+ 'cardName' => creditcard.name,
64
+ 'cardNumber' => creditcard.number,
65
+ 'cardExpiryMonth' => sprintf('%02d', creditcard.month),
66
+ 'cardExpiryYear' => sprintf('%02d', creditcard.year)
67
+ }
68
+ commit('addCard', post)
69
+ end
70
+
71
+ private
72
+
73
+ def add_transaction(post, identification)
74
+ post['transactionID'] = identification
75
+ end
76
+
77
+ def add_address(post, options)
78
+ return unless(address = options[:address])
79
+
80
+ post['customerName'] = address[:name]
81
+ post['customerCountry'] = address[:country]
82
+ post['customerState'] = address[:state]
83
+ post['customerCity'] = address[:city]
84
+ post['customerAddress'] = address[:address1]
85
+ post['customerPostCode'] = address[:zip]
86
+ end
87
+
88
+ def add_product(post, options)
89
+ post['transactionProduct'] = options[:transaction_product]
90
+ end
91
+
92
+ def add_payment_method(post, payment_method)
93
+ if payment_method.respond_to?(:number)
94
+ add_creditcard(post, payment_method)
95
+ else
96
+ add_token(post, payment_method)
97
+ end
98
+ end
99
+
100
+ def add_token(post, token)
101
+ post['cardID'] = token
102
+ end
103
+
104
+ def add_creditcard(post, creditcard)
105
+ post['paymentCardNumber'] = creditcard.number
106
+ post['paymentCardName'] = creditcard.name
107
+ post['paymentCardExpiry'] = creditcard.expiry_date.expiration.strftime("%m%y")
108
+ end
109
+
110
+ def add_amount(post, money, options)
111
+ currency = (options[:currency] || currency(money))
112
+
113
+ post['transactionAmount'] = money.to_s
114
+ post['transactionCurrency'] = currency
115
+ post['hash'] = verification_hash(money, currency)
116
+ end
117
+
118
+ def verification_hash(money, currency)
119
+ Digest::MD5.hexdigest(
120
+ (
121
+ @options[:api_passphrase].to_s +
122
+ @options[:merchant_uuid].to_s +
123
+ money.to_s +
124
+ currency
125
+ ).downcase
126
+ )
127
+ end
128
+
129
+ def parse(body)
130
+ xml = REXML::Document.new(body)
131
+
132
+ response = {}
133
+ xml.root.elements.to_a.each do |node|
134
+ parse_element(response, node)
135
+ end
136
+ response
137
+ end
138
+
139
+ def parse_element(response, node)
140
+ if node.has_elements?
141
+ node.elements.each{|element| parse_element(response, element)}
142
+ else
143
+ response[node.name.underscore.to_sym] = node.text
144
+ end
145
+ end
146
+
147
+ def commit(action, post)
148
+ add_auth(action, post)
149
+
150
+ response = parse(ssl_post(url_for(action, post), post_data(post)))
151
+
152
+ Response.new(
153
+ success?(response),
154
+ response[:response_message],
155
+ response,
156
+ :test => test?,
157
+ :authorization => (response[:card_id] || response[:transaction_id])
158
+ )
159
+ end
160
+
161
+ def add_auth(action, post)
162
+ post['merchantUUID'] = @options[:merchant_uuid]
163
+ post['apiKey'] = @options[:api_key]
164
+ unless token?(post)
165
+ post['method'] = action
166
+ end
167
+ end
168
+
169
+ def url_for(action, post)
170
+ if token?(post)
171
+ [(test? ? TOKEN_TEST_URL : TOKEN_LIVE_URL), action].join("/")
172
+ else
173
+ (test? ? POST_TEST_URL : POST_LIVE_URL)
174
+ end
175
+ end
176
+
177
+ def token?(post)
178
+ (post["cardID"] || post["cardName"])
179
+ end
180
+
181
+ def success?(response)
182
+ (response[:response_code] == '0')
183
+ end
184
+
185
+ def post_data(post)
186
+ post.collect{|k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
187
+ end
188
+ end
189
+ end
190
+ end