activemerchant 1.29.3 → 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 (47) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +39 -0
  3. data/CONTRIBUTORS +19 -0
  4. data/README.md +43 -41
  5. data/lib/active_merchant/billing/check.rb +15 -11
  6. data/lib/active_merchant/billing/credit_card.rb +5 -1
  7. data/lib/active_merchant/billing/credit_card_formatting.rb +8 -8
  8. data/lib/active_merchant/billing/gateway.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +9 -1
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +15 -4
  11. data/lib/active_merchant/billing/gateways/balanced.rb +9 -3
  12. data/lib/active_merchant/billing/gateways/banwire.rb +15 -1
  13. data/lib/active_merchant/billing/gateways/beanstream.rb +26 -24
  14. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +6 -2
  15. data/lib/active_merchant/billing/gateways/braintree_blue.rb +5 -2
  16. data/lib/active_merchant/billing/gateways/cyber_source.rb +55 -22
  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 +222 -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 +2 -4
  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/integrations/payflow_link/helper.rb +4 -1
  42. data/lib/active_merchant/billing/integrations/paypal/notification.rb +39 -31
  43. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +13 -10
  44. data/lib/active_merchant/billing/integrations/quickpay/notification.rb +14 -14
  45. data/lib/active_merchant/version.rb +1 -1
  46. metadata +109 -49
  47. metadata.gz.sig +0 -0
@@ -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
+
@@ -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]),