activemerchant 1.29.1 → 1.30.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 (50) hide show
  1. data/CHANGELOG +48 -0
  2. data/CONTRIBUTORS +19 -0
  3. data/README.md +43 -41
  4. data/lib/active_merchant/billing/check.rb +15 -11
  5. data/lib/active_merchant/billing/credit_card.rb +5 -1
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +8 -8
  7. data/lib/active_merchant/billing/gateway.rb +1 -1
  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/eway.rb +114 -171
  17. data/lib/active_merchant/billing/gateways/eway_managed.rb +52 -22
  18. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +222 -0
  19. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +13 -2
  20. data/lib/active_merchant/billing/gateways/litle.rb +50 -19
  21. data/lib/active_merchant/billing/gateways/merchant_ware.rb +44 -9
  22. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +190 -0
  23. data/lib/active_merchant/billing/gateways/moneris.rb +3 -5
  24. data/lib/active_merchant/billing/gateways/moneris_us.rb +1 -1
  25. data/lib/active_merchant/billing/gateways/nab_transact.rb +20 -3
  26. data/lib/active_merchant/billing/gateways/netbilling.rb +1 -0
  27. data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
  28. data/lib/active_merchant/billing/gateways/optimal_payment.rb +18 -3
  29. data/lib/active_merchant/billing/gateways/orbital.rb +9 -5
  30. data/lib/active_merchant/billing/gateways/payment_express.rb +62 -1
  31. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
  32. data/lib/active_merchant/billing/gateways/paypal_express.rb +2 -0
  33. data/lib/active_merchant/billing/gateways/pin.rb +157 -0
  34. data/lib/active_merchant/billing/gateways/qbms.rb +3 -2
  35. data/lib/active_merchant/billing/gateways/quickpay.rb +66 -28
  36. data/lib/active_merchant/billing/gateways/sage_pay.rb +6 -0
  37. data/lib/active_merchant/billing/gateways/smart_ps.rb +1 -1
  38. data/lib/active_merchant/billing/gateways/spreedly_core.rb +235 -0
  39. data/lib/active_merchant/billing/gateways/stripe.rb +1 -0
  40. data/lib/active_merchant/billing/gateways/wirecard.rb +15 -9
  41. data/lib/active_merchant/billing/gateways/worldpay.rb +15 -4
  42. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +4 -1
  43. data/lib/active_merchant/billing/integrations/paypal/notification.rb +39 -31
  44. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +13 -10
  45. data/lib/active_merchant/billing/integrations/quickpay/notification.rb +14 -14
  46. data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +2 -2
  47. data/lib/active_merchant/version.rb +1 -1
  48. data.tar.gz.sig +0 -0
  49. metadata +109 -49
  50. metadata.gz.sig +0 -0
@@ -2,137 +2,10 @@ require 'rexml/document'
2
2
 
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
- # First, make sure you have everything setup correctly and all of your dependencies in place with:
6
- #
7
- # require 'rubygems'
8
- # require 'active_merchant'
9
- #
10
- # ActiveMerchant expects the amounts to be given as an Integer in cents. In this case, $10 US becomes 1000.
11
- #
12
- # tendollar = 1000
13
- #
14
- # The transaction result is based on the cent value of the transaction. $10.15 will return a failed transaction
15
- # with a response code of "15 – No Issuer", while $10.00 will return "00 – Transaction Approved."
16
- #
17
- # Next, create a credit card object using a eWay approved test card number (4444333322221111).
18
- #
19
- # creditcard = ActiveMerchant::Billing::CreditCard.new(
20
- # :number => '4444333322221111',
21
- # :month => 8,
22
- # :year => 2006,
23
- # :first_name => 'Longbob',
24
- # :last_name => 'Longsen',
25
- # :verification_value => '123'
26
- # )
27
- # options = {
28
- # :order_id => '1230123',
29
- # :email => 'bob@testbob.com',
30
- # :address => { :address1 => '47 Bobway',
31
- # :city => 'Bobville',
32
- # :state => 'WA',
33
- # :country => 'Australia',
34
- # :zip => '2000'
35
- # },
36
- # :description => 'purchased items'
37
- # }
38
- #
39
- # To finish setting up, create the active_merchant object you will be using, with the eWay gateway. If you have a
40
- # functional eWay account, replace :login with your Customer ID.
41
- #
42
- # gateway = ActiveMerchant::Billing::Base.gateway(:eway).new(:login => '87654321')
43
- #
44
- # Now we are ready to process our transaction
45
- #
46
- # response = gateway.purchase(tendollar, creditcard, options)
47
- #
48
- # Sending a transaction to eWay with active_merchant returns a Response object, which consistently allows you to:
49
- #
50
- # 1) Check whether the transaction was successful
51
- #
52
- # response.success?
53
- #
54
- # 2) Retrieve any message returned by eWay, either a "transaction was successful" note or an explanation of why the
55
- # transaction was rejected.
56
- #
57
- # response.message
58
- #
59
- # 3) Retrieve and store the unique transaction ID returned by eWway, for use in referencing the transaction in the future.
60
- #
61
- # response.authorization
62
- #
63
- # This should be enough to get you started with eWay and active_merchant. For further information, review the methods
64
- # below and the rest of active_merchant's documentation.
65
-
5
+ # Public: For more information on the Eway Gateway please visit their
6
+ # {Developers Area}[http://www.eway.com.au/developers/api/direct-payments]
66
7
  class EwayGateway < Gateway
67
- self.test_url = 'https://www.eway.com.au/gateway/xmltest/testpage.asp'
68
- self.live_url = 'https://www.eway.com.au/gateway/xmlpayment.asp'
69
-
70
- class_attribute :test_cvn_url, :live_cvn_url
71
- self.test_cvn_url = 'https://www.eway.com.au/gateway_cvn/xmltest/testpage.asp'
72
- self.live_cvn_url = 'https://www.eway.com.au/gateway_cvn/xmlpayment.asp'
73
-
74
- MESSAGES = {
75
- "00" => "Transaction Approved",
76
- "01" => "Refer to Issuer",
77
- "02" => "Refer to Issuer, special",
78
- "03" => "No Merchant",
79
- "04" => "Pick Up Card",
80
- "05" => "Do Not Honour",
81
- "06" => "Error",
82
- "07" => "Pick Up Card, Special",
83
- "08" => "Honour With Identification",
84
- "09" => "Request In Progress",
85
- "10" => "Approved For Partial Amount",
86
- "11" => "Approved, VIP",
87
- "12" => "Invalid Transaction",
88
- "13" => "Invalid Amount",
89
- "14" => "Invalid Card Number",
90
- "15" => "No Issuer",
91
- "16" => "Approved, Update Track 3",
92
- "19" => "Re-enter Last Transaction",
93
- "21" => "No Action Taken",
94
- "22" => "Suspected Malfunction",
95
- "23" => "Unacceptable Transaction Fee",
96
- "25" => "Unable to Locate Record On File",
97
- "30" => "Format Error",
98
- "31" => "Bank Not Supported By Switch",
99
- "33" => "Expired Card, Capture",
100
- "34" => "Suspected Fraud, Retain Card",
101
- "35" => "Card Acceptor, Contact Acquirer, Retain Card",
102
- "36" => "Restricted Card, Retain Card",
103
- "37" => "Contact Acquirer Security Department, Retain Card",
104
- "38" => "PIN Tries Exceeded, Capture",
105
- "39" => "No Credit Account",
106
- "40" => "Function Not Supported",
107
- "41" => "Lost Card",
108
- "42" => "No Universal Account",
109
- "43" => "Stolen Card",
110
- "44" => "No Investment Account",
111
- "51" => "Insufficient Funds",
112
- "52" => "No Cheque Account",
113
- "53" => "No Savings Account",
114
- "54" => "Expired Card",
115
- "55" => "Incorrect PIN",
116
- "56" => "No Card Record",
117
- "57" => "Function Not Permitted to Cardholder",
118
- "58" => "Function Not Permitted to Terminal",
119
- "59" => "Suspected Fraud",
120
- "60" => "Acceptor Contact Acquirer",
121
- "61" => "Exceeds Withdrawal Limit",
122
- "62" => "Restricted Card",
123
- "63" => "Security Violation",
124
- "64" => "Original Amount Incorrect",
125
- "66" => "Acceptor Contact Acquirer, Security",
126
- "67" => "Capture Card",
127
- "75" => "PIN Tries Exceeded",
128
- "82" => "CVV Validation Error",
129
- "90" => "Cutoff In Progress",
130
- "91" => "Card Issuer Unavailable",
131
- "92" => "Unable To Route Transaction",
132
- "93" => "Cannot Complete, Violation Of The Law",
133
- "94" => "Duplicate Transaction",
134
- "96" => "System Error"
135
- }
8
+ self.live_url = 'https://www.eway.com.au'
136
9
 
137
10
  self.money_format = :cents
138
11
  self.supported_countries = ['AU']
@@ -140,28 +13,46 @@ module ActiveMerchant #:nodoc:
140
13
  self.homepage_url = 'http://www.eway.com.au/'
141
14
  self.display_name = 'eWAY'
142
15
 
16
+ # Public: Create a new Eway Gateway.
17
+ # options - A hash of options:
18
+ # :login - Your Customer ID.
19
+ # :password - Your XML Refund Password that you
20
+ # specified on the Eway site. (optional)
143
21
  def initialize(options = {})
144
22
  requires!(options, :login)
145
23
  super
146
24
  end
147
25
 
148
- # ewayCustomerEmail, ewayCustomerAddress, ewayCustomerPostcode
149
26
  def purchase(money, creditcard, options = {})
150
27
  requires_address!(options)
151
28
 
152
29
  post = {}
153
30
  add_creditcard(post, creditcard)
154
31
  add_address(post, options)
155
- add_customer_data(post, options)
32
+ add_customer_id(post)
156
33
  add_invoice_data(post, options)
157
- # The request fails if all of the fields aren't present
158
- add_optional_data(post)
34
+ add_non_optional_data(post)
35
+ add_amount(post, money)
36
+ post[:CustomerEmail] = options[:email]
159
37
 
160
- commit(money, post)
38
+ commit(purchase_url(post[:CVN]), money, post)
161
39
  end
162
40
 
163
- private
41
+ def refund(money, authorization, options={})
42
+ post = {}
164
43
 
44
+ add_customer_id(post)
45
+ add_amount(post, money)
46
+ add_non_optional_data(post)
47
+ post[:OriginalTrxnNumber] = authorization
48
+ post[:RefundPassword] = @options[:password]
49
+ post[:CardExpiryMonth] = nil
50
+ post[:CardExpiryYear] = nil
51
+
52
+ commit(refund_url, money, post)
53
+ end
54
+
55
+ private
165
56
  def requires_address!(options)
166
57
  raise ArgumentError.new("Missing eWay required parameters: address or billing_address") unless (options.has_key?(:address) or options.has_key?(:billing_address))
167
58
  end
@@ -184,8 +75,8 @@ module ActiveMerchant #:nodoc:
184
75
  end
185
76
  end
186
77
 
187
- def add_customer_data(post, options)
188
- post[:CustomerEmail] = options[:email]
78
+ def add_customer_id(post)
79
+ post[:CustomerID] = @options[:login]
189
80
  end
190
81
 
191
82
  def add_invoice_data(post, options)
@@ -193,21 +84,26 @@ module ActiveMerchant #:nodoc:
193
84
  post[:CustomerInvoiceDescription] = options[:description]
194
85
  end
195
86
 
196
- def add_optional_data(post)
197
- post[:TrxnNumber] = nil
87
+ def add_amount(post, money)
88
+ post[:TotalAmount] = amount(money)
89
+ end
90
+
91
+ def add_non_optional_data(post)
198
92
  post[:Option1] = nil
199
93
  post[:Option2] = nil
200
94
  post[:Option3] = nil
95
+ post[:TrxnNumber] = nil
201
96
  end
202
97
 
203
- def commit(money, parameters)
204
- parameters[:TotalAmount] = amount(money)
98
+ def commit(url, money, parameters)
99
+ raw_response = ssl_post(url, post_data(parameters))
100
+ response = parse(raw_response)
205
101
 
206
- response = parse( ssl_post(gateway_url(parameters[:CVN], test?), post_data(parameters)) )
207
-
208
- Response.new(success?(response), message_from(response[:ewaytrxnerror]), response,
209
- :authorization => response[:ewayauthcode],
210
- :test => /\(Test( CVN)? Gateway\)/ === response[:ewaytrxnerror]
102
+ Response.new(success?(response),
103
+ message_from(response[:ewaytrxnerror]),
104
+ response,
105
+ :authorization => response[:ewaytrxnnumber],
106
+ :test => test?
211
107
  )
212
108
  end
213
109
 
@@ -215,35 +111,17 @@ module ActiveMerchant #:nodoc:
215
111
  response[:ewaytrxnstatus] == "True"
216
112
  end
217
113
 
218
- # Parse eway response xml into a convinient hash
219
114
  def parse(xml)
220
- # "<?xml version=\"1.0\"?>".
221
- # <ewayResponse>
222
- # <ewayTrxnError></ewayTrxnError>
223
- # <ewayTrxnStatus>True</ewayTrxnStatus>
224
- # <ewayTrxnNumber>10002</ewayTrxnNumber>
225
- # <ewayTrxnOption1></ewayTrxnOption1>
226
- # <ewayTrxnOption2></ewayTrxnOption2>
227
- # <ewayTrxnOption3></ewayTrxnOption3>
228
- # <ewayReturnAmount>10</ewayReturnAmount>
229
- # <ewayAuthCode>123456</ewayAuthCode>
230
- # <ewayTrxnReference>987654321</ewayTrxnReference>
231
- # </ewayResponse>
232
-
233
115
  response = {}
234
116
  xml = REXML::Document.new(xml)
235
117
  xml.elements.each('//ewayResponse/*') do |node|
236
-
237
118
  response[node.name.downcase.to_sym] = normalize(node.text)
238
-
239
119
  end unless xml.root.nil?
240
120
 
241
121
  response
242
122
  end
243
123
 
244
124
  def post_data(parameters = {})
245
- parameters[:CustomerID] = @options[:login]
246
-
247
125
  xml = REXML::Document.new
248
126
  root = xml.add_element("ewaygateway")
249
127
 
@@ -269,14 +147,79 @@ module ActiveMerchant #:nodoc:
269
147
  end
270
148
  end
271
149
 
272
- def gateway_url(cvn, test)
273
- if cvn
274
- test ? self.test_cvn_url : self.live_cvn_url
275
- else
276
- test ? self.test_url : self.live_url
277
- end
150
+ def purchase_url(cvn)
151
+ suffix = test? ? 'xmltest/testpage.asp' : 'xmlpayment.asp'
152
+ gateway_part = cvn ? 'gateway_cvn' : 'gateway'
153
+ "#{live_url}/#{gateway_part}/#{suffix}"
278
154
  end
279
155
 
156
+ def refund_url
157
+ suffix = test? ? 'xmltest/refund_test.asp' : 'xmlpaymentrefund.asp'
158
+ "#{live_url}/gateway/#{suffix}"
159
+ end
160
+
161
+ MESSAGES = {
162
+ "00" => "Transaction Approved",
163
+ "01" => "Refer to Issuer",
164
+ "02" => "Refer to Issuer, special",
165
+ "03" => "No Merchant",
166
+ "04" => "Pick Up Card",
167
+ "05" => "Do Not Honour",
168
+ "06" => "Error",
169
+ "07" => "Pick Up Card, Special",
170
+ "08" => "Honour With Identification",
171
+ "09" => "Request In Progress",
172
+ "10" => "Approved For Partial Amount",
173
+ "11" => "Approved, VIP",
174
+ "12" => "Invalid Transaction",
175
+ "13" => "Invalid Amount",
176
+ "14" => "Invalid Card Number",
177
+ "15" => "No Issuer",
178
+ "16" => "Approved, Update Track 3",
179
+ "19" => "Re-enter Last Transaction",
180
+ "21" => "No Action Taken",
181
+ "22" => "Suspected Malfunction",
182
+ "23" => "Unacceptable Transaction Fee",
183
+ "25" => "Unable to Locate Record On File",
184
+ "30" => "Format Error",
185
+ "31" => "Bank Not Supported By Switch",
186
+ "33" => "Expired Card, Capture",
187
+ "34" => "Suspected Fraud, Retain Card",
188
+ "35" => "Card Acceptor, Contact Acquirer, Retain Card",
189
+ "36" => "Restricted Card, Retain Card",
190
+ "37" => "Contact Acquirer Security Department, Retain Card",
191
+ "38" => "PIN Tries Exceeded, Capture",
192
+ "39" => "No Credit Account",
193
+ "40" => "Function Not Supported",
194
+ "41" => "Lost Card",
195
+ "42" => "No Universal Account",
196
+ "43" => "Stolen Card",
197
+ "44" => "No Investment Account",
198
+ "51" => "Insufficient Funds",
199
+ "52" => "No Cheque Account",
200
+ "53" => "No Savings Account",
201
+ "54" => "Expired Card",
202
+ "55" => "Incorrect PIN",
203
+ "56" => "No Card Record",
204
+ "57" => "Function Not Permitted to Cardholder",
205
+ "58" => "Function Not Permitted to Terminal",
206
+ "59" => "Suspected Fraud",
207
+ "60" => "Acceptor Contact Acquirer",
208
+ "61" => "Exceeds Withdrawal Limit",
209
+ "62" => "Restricted Card",
210
+ "63" => "Security Violation",
211
+ "64" => "Original Amount Incorrect",
212
+ "66" => "Acceptor Contact Acquirer, Security",
213
+ "67" => "Capture Card",
214
+ "75" => "PIN Tries Exceeded",
215
+ "82" => "CVV Validation Error",
216
+ "90" => "Cutoff In Progress",
217
+ "91" => "Card Issuer Unavailable",
218
+ "92" => "Unable To Route Transaction",
219
+ "93" => "Cannot Complete, Violation Of The Law",
220
+ "94" => "Duplicate Transaction",
221
+ "96" => "System Error"
222
+ }
280
223
  end
281
224
  end
282
225
  end
@@ -89,7 +89,18 @@ module ActiveMerchant #:nodoc:
89
89
  commit("ProcessPayment", post)
90
90
  end
91
91
 
92
- # TODO: eWay API also provides QueryCustomer
92
+ # Get customer's stored credit card details given by billing_id
93
+ #
94
+ # ==== Parameters
95
+ #
96
+ # * <tt>billing_id</tt> -- The eWay provided card/customer token to charge (managedCustomerID)
97
+ def retrieve(billing_id)
98
+ post = {}
99
+ post[:managedCustomerID] = billing_id.to_s
100
+
101
+ commit("QueryCustomer", post)
102
+ end
103
+
93
104
  # TODO: eWay API also provides QueryPayment
94
105
 
95
106
  private
@@ -146,25 +157,29 @@ module ActiveMerchant #:nodoc:
146
157
  # Successful payment
147
158
  reply=parse_purchase(root)
148
159
  else
149
- if root = REXML::XPath.first(xml, '//CreateCustomerResult') then
150
- reply[:message]='OK'
151
- reply[:CreateCustomerResult]=root.text
152
- reply[:success]=true
160
+ if root = REXML::XPath.first(xml, '//QueryCustomerResult') then
161
+ reply=parse_query_customer(root)
153
162
  else
154
- if root = REXML::XPath.first(xml, '//UpdateCustomerResult') then
155
- if root.text.downcase == 'true' then
156
- reply[:message]='OK'
157
- reply[:success]=true
163
+ if root = REXML::XPath.first(xml, '//CreateCustomerResult') then
164
+ reply[:message]='OK'
165
+ reply[:CreateCustomerResult]=root.text
166
+ reply[:success]=true
167
+ else
168
+ if root = REXML::XPath.first(xml, '//UpdateCustomerResult') then
169
+ if root.text.downcase == 'true' then
170
+ reply[:message]='OK'
171
+ reply[:success]=true
172
+ else
173
+ # ERROR: This state should never occur. If there is a problem,
174
+ # a soap:Fault will be returned. The presence of this
175
+ # element always means a success.
176
+ raise StandardError, "Unexpected \"false\" in UpdateCustomerResult"
177
+ end
158
178
  else
159
- # ERROR: This state should never occur. If there is a problem,
160
- # a soap:Fault will be returned. The presence of this
161
- # element always means a success.
162
- raise StandardError, "Unexpected \"false\" in UpdateCustomerResult"
179
+ # ERROR: This state should never occur currently. We have handled
180
+ # responses for all the methods which we support.
181
+ raise StandardError, "Unexpected response"
163
182
  end
164
- else
165
- # ERROR: This state should never occur currently. We have handled
166
- # responses for all the methods which we support.
167
- raise StandardError, "Unexpected response"
168
183
  end
169
184
  end
170
185
  end
@@ -188,6 +203,17 @@ module ActiveMerchant #:nodoc:
188
203
  reply
189
204
  end
190
205
 
206
+ def parse_query_customer(node)
207
+ reply={}
208
+ reply[:message]='OK'
209
+ reply[:success]=true
210
+ reply[:CCNumber]=REXML::XPath.first(node, '//CCNumber').text
211
+ reply[:CCName]=REXML::XPath.first(node, '//CCName').text
212
+ reply[:CCExpiryMonth]=REXML::XPath.first(node, '//CCExpiryMonth').text
213
+ reply[:CCExpiryYear]=REXML::XPath.first(node, '//CCExpiryYear').text
214
+ reply
215
+ end
216
+
191
217
  def commit(action, post)
192
218
  raw = begin
193
219
  ssl_post(test? ? self.test_url : self.live_url, soap_request(post, action), 'Content-Type' => 'application/soap+xml; charset=utf-8')
@@ -206,11 +232,15 @@ module ActiveMerchant #:nodoc:
206
232
  def soap_request(arguments, action)
207
233
  # eWay demands all fields be sent, but contain an empty string if blank
208
234
  post = case action
209
- when 'ProcessPayment'
210
- default_payment_fields.merge(arguments)
211
- else
212
- default_customer_fields.merge(arguments)
213
- end
235
+ when 'QueryCustomer'
236
+ arguments
237
+ when 'ProcessPayment'
238
+ default_payment_fields.merge(arguments)
239
+ when 'CreateCustomer'
240
+ default_customer_fields.merge(arguments)
241
+ when 'UpdateCustomer'
242
+ default_customer_fields.merge(arguments)
243
+ end
214
244
 
215
245
  xml = Builder::XmlMarkup.new :indent => 2
216
246
  xml.instruct!
@@ -0,0 +1,222 @@
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
+ def initialize(options = {})
31
+ requires!(options, :login, :password)
32
+ @options = options
33
+
34
+ super
35
+ end
36
+
37
+ def authorize(money, credit_card, options = {})
38
+ commit(:authorization, build_sale_or_authorization_request(money, credit_card, options))
39
+ end
40
+
41
+ def purchase(money, credit_card, options = {})
42
+ commit(:sale, build_sale_or_authorization_request(money, credit_card, options))
43
+ end
44
+
45
+ def capture(money, authorization, options = {})
46
+ commit(:capture, build_capture_or_credit_request(money, authorization, options))
47
+ end
48
+
49
+ def void(authorization, options = {})
50
+ commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options))
51
+ end
52
+
53
+ def refund(money, authorization, options = {})
54
+ commit(:credit, build_capture_or_credit_request(money, authorization, options))
55
+ end
56
+
57
+ private
58
+
59
+ def build_request(action, body)
60
+ xml = Builder::XmlMarkup.new
61
+
62
+ xml.instruct!
63
+ xml.tag! "Transaction" do
64
+ add_credentials(xml)
65
+ add_transaction_type(xml, action)
66
+ xml << body
67
+ end
68
+
69
+ xml.target!
70
+ end
71
+
72
+ def build_sale_or_authorization_request(money, credit_card, options)
73
+ xml = Builder::XmlMarkup.new
74
+
75
+ add_amount(xml, money)
76
+ add_credit_card(xml, credit_card)
77
+ add_customer_data(xml, options)
78
+ add_invoice(xml, options)
79
+
80
+ xml.target!
81
+ end
82
+
83
+ def build_capture_or_credit_request(money, identification, options)
84
+ xml = Builder::XmlMarkup.new
85
+
86
+ add_identification(xml, identification)
87
+ add_amount(xml, money)
88
+ add_customer_data(xml, options)
89
+
90
+ xml.target!
91
+ end
92
+
93
+ def add_credentials(xml)
94
+ xml.tag! "ExactID", @options[:login]
95
+ xml.tag! "Password", @options[:password]
96
+ end
97
+
98
+ def add_transaction_type(xml, action)
99
+ xml.tag! "Transaction_Type", TRANSACTIONS[action]
100
+ end
101
+
102
+ def add_identification(xml, identification)
103
+ authorization_num, transaction_tag, _ = identification.split(";")
104
+
105
+ xml.tag! "Authorization_Num", authorization_num
106
+ xml.tag! "Transaction_Tag", transaction_tag
107
+ end
108
+
109
+ def add_amount(xml, money)
110
+ xml.tag! "DollarAmount", amount(money)
111
+ end
112
+
113
+ def add_credit_card(xml, credit_card)
114
+ xml.tag! "Card_Number", credit_card.number
115
+ xml.tag! "Expiry_Date", expdate(credit_card)
116
+ xml.tag! "CardHoldersName", credit_card.name
117
+ xml.tag! "CardType", credit_card.brand
118
+
119
+ if credit_card.verification_value?
120
+ xml.tag! "CVD_Presence_Ind", "1"
121
+ xml.tag! "VerificationStr2", credit_card.verification_value
122
+ end
123
+ end
124
+
125
+ def add_customer_data(xml, options)
126
+ xml.tag! "Customer_Ref", options[:customer] if options[:customer]
127
+ xml.tag! "Client_IP", options[:ip] if options[:ip]
128
+ xml.tag! "Client_Email", options[:email] if options[:email]
129
+ end
130
+
131
+ def add_address(xml, options)
132
+ if address = (options[:billing_address] || options[:address])
133
+ xml.tag! "ZipCode", address[:zip]
134
+ end
135
+ end
136
+
137
+ def add_invoice(xml, options)
138
+ xml.tag! "Reference_No", options[:order_id]
139
+ xml.tag! "Reference_3", options[:description] if options[:description]
140
+ end
141
+
142
+ def expdate(credit_card)
143
+ "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
144
+ end
145
+
146
+ def commit(action, request)
147
+ url = (test? ? self.test_url : self.live_url)
148
+ begin
149
+ response = parse(ssl_post(url, build_request(action, request), POST_HEADERS))
150
+ rescue ResponseError => e
151
+ response = parse_error(e.response)
152
+ end
153
+
154
+ Response.new(successful?(response), message_from(response), response,
155
+ :test => test?,
156
+ :authorization => authorization_from(response),
157
+ :avs_result => {:code => response[:avs]},
158
+ :cvv_result => response[:cvv2]
159
+ )
160
+ end
161
+
162
+ def successful?(response)
163
+ response[:transaction_approved] == SUCCESS
164
+ end
165
+
166
+ def authorization_from(response)
167
+ if response[:authorization_num] && response[:transaction_tag]
168
+ [
169
+ response[:authorization_num],
170
+ response[:transaction_tag],
171
+ (response[:dollar_amount].to_f * 100).to_i
172
+ ].join(";")
173
+ else
174
+ ""
175
+ end
176
+ end
177
+
178
+ def money_from_authorization(auth)
179
+ _, _, amount = auth.split(/;/, 3)
180
+ amount.to_i # return the # of cents, no need to divide
181
+ end
182
+
183
+ def message_from(response)
184
+ if(response[:faultcode] && response[:faultstring])
185
+ response[:faultstring]
186
+ elsif(response[:error_number] != "0")
187
+ response[:error_description]
188
+ else
189
+ result = (response[:exact_message] || "")
190
+ result << " - #{response[:bank_message]}" if response[:bank_message].present?
191
+ result
192
+ end
193
+ end
194
+
195
+ def parse_error(error)
196
+ {
197
+ :transaction_approved => "false",
198
+ :error_number => error.code,
199
+ :error_description => error.body
200
+ }
201
+ end
202
+
203
+ def parse(xml)
204
+ response = {}
205
+ xml = REXML::Document.new(xml)
206
+
207
+ if root = REXML::XPath.first(xml, "//TransactionResult")
208
+ parse_elements(response, root)
209
+ end
210
+
211
+ response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) }
212
+ end
213
+
214
+ def parse_elements(response, root)
215
+ root.elements.to_a.each do |node|
216
+ response[node.name.gsub(/EXact/, "Exact").underscore.to_sym] = (node.text || "").strip
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+