activemerchant 1.27.0 → 1.28.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -101,6 +101,10 @@ module ActiveMerchant #:nodoc:
101
101
  build_response(:registerToken, @litle.register_token_request(to_pass), %w(801 802))
102
102
  end
103
103
 
104
+ def test?
105
+ super || @options[:test]
106
+ end
107
+
104
108
  private
105
109
 
106
110
  CARD_TYPE = {
@@ -148,10 +152,11 @@ module ActiveMerchant #:nodoc:
148
152
  {:litleOnlineResponse => response},
149
153
  :authorization => detail['litleTxnId'],
150
154
  :avs_result => {:code => fraud['avs']},
151
- :cvv_result => fraud['cvv']
155
+ :cvv_result => fraud['cvv'],
156
+ :test => test?
152
157
  )
153
158
  else
154
- Response.new(false, response['message'], :litleOnlineResponse => response)
159
+ Response.new(false, response['message'], :litleOnlineResponse => response, :test => test?)
155
160
  end
156
161
  end
157
162
 
@@ -4,32 +4,32 @@ require "rexml/document"
4
4
  module ActiveMerchant #:nodoc:
5
5
  module Billing #:nodoc:
6
6
  # For more information on Orbital, visit the {integration center}[http://download.chasepaymentech.com]
7
- #
7
+ #
8
8
  # ==== Authentication Options
9
- #
9
+ #
10
10
  # The Orbital Gateway supports two methods of authenticating incoming requests:
11
11
  # Source IP authentication and Connection Username/Password authentication
12
- #
13
- # In addition, these IP addresses/Connection Usernames must be affiliated with the Merchant IDs
12
+ #
13
+ # In addition, these IP addresses/Connection Usernames must be affiliated with the Merchant IDs
14
14
  # for which the client should be submitting transactions.
15
- #
16
- # This does allow Third Party Hosting service organizations presenting on behalf of other
17
- # merchants to submit transactions. However, each time a new customer is added, the
18
- # merchant or Third-Party hosting organization needs to ensure that the new Merchant IDs
15
+ #
16
+ # This does allow Third Party Hosting service organizations presenting on behalf of other
17
+ # merchants to submit transactions. However, each time a new customer is added, the
18
+ # merchant or Third-Party hosting organization needs to ensure that the new Merchant IDs
19
19
  # or Chain IDs are affiliated with the hosting companies IPs or Connection Usernames.
20
- #
21
- # If the merchant expects to have more than one merchant account with the Orbital
22
- # Gateway, it should have its IP addresses/Connection Usernames affiliated at the Chain
20
+ #
21
+ # If the merchant expects to have more than one merchant account with the Orbital
22
+ # Gateway, it should have its IP addresses/Connection Usernames affiliated at the Chain
23
23
  # level hierarchy within the Orbital Gateway. Each time a new merchant ID is added, as
24
- # long as it is placed within the same Chain, it will simply work. Otherwise, the additional
24
+ # long as it is placed within the same Chain, it will simply work. Otherwise, the additional
25
25
  # MIDs will need to be affiliated with the merchant IPs or Connection Usernames respectively.
26
- # For example, we generally affiliate all Salem accounts [BIN 000001] with
27
- # their Company Number [formerly called MA #] number so all MIDs or Divisions under that
26
+ # For example, we generally affiliate all Salem accounts [BIN 000001] with
27
+ # their Company Number [formerly called MA #] number so all MIDs or Divisions under that
28
28
  # Company will automatically be affiliated.
29
-
29
+
30
30
  class OrbitalGateway < Gateway
31
- API_VERSION = "4.6"
32
-
31
+ API_VERSION = "5.6"
32
+
33
33
  POST_HEADERS = {
34
34
  "MIME-Version" => "1.0",
35
35
  "Content-Type" => "Application/PTI46",
@@ -38,27 +38,29 @@ module ActiveMerchant #:nodoc:
38
38
  "Document-type" => "Request",
39
39
  "Interface-Version" => "Ruby|ActiveMerchant|Proprietary Gateway"
40
40
  }
41
-
41
+
42
42
  SUCCESS, APPROVED = '0', '00'
43
-
43
+
44
44
  class_attribute :secondary_test_url, :secondary_live_url
45
-
45
+
46
46
  self.test_url = "https://orbitalvar1.paymentech.net/authorize"
47
47
  self.secondary_test_url = "https://orbitalvar2.paymentech.net/authorize"
48
-
48
+
49
49
  self.live_url = "https://orbital1.paymentech.net/authorize"
50
50
  self.secondary_live_url = "https://orbital2.paymentech.net/authorize"
51
-
51
+
52
52
  self.supported_countries = ["US", "CA"]
53
53
  self.default_currency = "CAD"
54
54
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
55
-
55
+
56
56
  self.display_name = 'Orbital Paymentech'
57
57
  self.homepage_url = 'http://chasepaymentech.com/'
58
-
58
+
59
59
  self.money_format = :cents
60
-
61
- CURRENCY_CODES = {
60
+
61
+ AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB']
62
+
63
+ CURRENCY_CODES = {
62
64
  "AUD" => '036',
63
65
  "CAD" => '124',
64
66
  "CZK" => '203',
@@ -77,42 +79,43 @@ module ActiveMerchant #:nodoc:
77
79
  "EUR" => '978'
78
80
  }
79
81
 
80
- AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB']
81
-
82
82
  def initialize(options = {})
83
83
  requires!(options, :merchant_id)
84
84
  requires!(options, :login, :password) unless options[:ip_authentication]
85
85
  @options = options
86
86
  super
87
87
  end
88
-
88
+
89
89
  # A – Authorization request
90
90
  def authorize(money, creditcard, options = {})
91
91
  order = build_new_order_xml('A', money, options) do |xml|
92
- add_creditcard(xml, creditcard, options[:currency])
93
- add_address(xml, creditcard, options)
92
+ add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn]
93
+ add_address(xml, creditcard, options)
94
+ add_customer_data(xml, options) if @options[:customer_profiles]
94
95
  end
95
96
  commit(order)
96
97
  end
97
-
98
+
98
99
  # AC – Authorization and Capture
99
100
  def purchase(money, creditcard, options = {})
100
101
  order = build_new_order_xml('AC', money, options) do |xml|
101
- add_creditcard(xml, creditcard, options[:currency])
102
- add_address(xml, creditcard, options)
102
+ add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn]
103
+ add_address(xml, creditcard, options)
104
+ add_customer_data(xml, options) if @options[:customer_profiles]
103
105
  end
104
106
  commit(order)
105
- end
106
-
107
+ end
108
+
107
109
  # MFC - Mark For Capture
108
110
  def capture(money, authorization, options = {})
109
111
  commit(build_mark_for_capture_xml(money, authorization, options))
110
112
  end
111
-
113
+
112
114
  # R – Refund request
113
115
  def refund(money, authorization, options = {})
114
116
  order = build_new_order_xml('R', money, options.merge(:authorization => authorization)) do |xml|
115
117
  add_refund(xml, options[:currency])
118
+ xml.tag! :CustomerRefNum, options[:customer_ref_num] if @options[:customer_profiles] && options[:profile_txn]
116
119
  end
117
120
  commit(order)
118
121
  end
@@ -121,31 +124,90 @@ module ActiveMerchant #:nodoc:
121
124
  deprecated CREDIT_DEPRECATION_MESSAGE
122
125
  refund(money, authorization, options)
123
126
  end
124
-
127
+
125
128
  # setting money to nil will perform a full void
126
- def void(authorization, options = {})
127
- order = build_void_request_xml(authorization, options)
129
+ def void(money, authorization, options = {})
130
+ order = build_void_request_xml(money, authorization, options)
131
+ commit(order)
132
+ end
133
+
134
+
135
+ # ==== Customer Profiles
136
+ # :customer_ref_num should be set unless your happy with Orbital providing one
137
+ #
138
+ # :customer_profile_order_override_ind can be set to map
139
+ # the CustomerRefNum to OrderID or Comments. Defaults to 'NO' - no mapping
140
+ #
141
+ # 'NO' - No mapping to order data
142
+ # 'OI' - Use <CustomerRefNum> for <OrderID>
143
+ # 'OD' - Use <CustomerRefNum> for <Comments>
144
+ # 'OA' - Use <CustomerRefNum> for <OrderID> and <Comments>
145
+ #
146
+ # :order_default_description can be set optionally. 64 char max.
147
+ #
148
+ # :order_default_amount can be set optionally. integer as cents.
149
+ #
150
+ # :status defaults to Active
151
+ #
152
+ # 'A' - Active
153
+ # 'I' - Inactive
154
+ # 'MS' - Manual Suspend
155
+
156
+ def add_customer_profile(creditcard, options = {})
157
+ options.merge!(:customer_profile_action => 'C')
158
+ order = build_customer_request_xml(creditcard, options)
159
+ commit(order)
160
+ end
161
+
162
+ def update_customer_profile(creditcard, options = {})
163
+ options.merge!(:customer_profile_action => 'U')
164
+ order = build_customer_request_xml(creditcard, options)
165
+ commit(order)
166
+ end
167
+
168
+ def retrieve_customer_profile(customer_ref_num)
169
+ options = {:customer_profile_action => 'R', :customer_ref_num => customer_ref_num}
170
+ order = build_customer_request_xml(nil, options)
171
+ commit(order)
172
+ end
173
+
174
+ def delete_customer_profile(customer_ref_num)
175
+ options = {:customer_profile_action => 'D', :customer_ref_num => customer_ref_num}
176
+ order = build_customer_request_xml(nil, options)
128
177
  commit(order)
129
178
  end
130
-
131
- private
132
-
179
+
180
+ private
181
+
182
+ def authorization_string(*args)
183
+ args.compact.join(";")
184
+ end
185
+
186
+ def split_authorization(authorization)
187
+ authorization.split(';')
188
+ end
189
+
133
190
  def add_customer_data(xml, options)
134
- if options[:customer_ref_num]
135
- xml.tag! :CustomerProfileFromOrderInd, 'S'
191
+ if options[:profile_txn]
136
192
  xml.tag! :CustomerRefNum, options[:customer_ref_num]
137
193
  else
138
- xml.tag! :CustomerProfileFromOrderInd, 'A'
194
+ if options[:customer_ref_num]
195
+ xml.tag! :CustomerProfileFromOrderInd, 'S'
196
+ xml.tag! :CustomerRefNum, options[:customer_ref_num]
197
+ else
198
+ xml.tag! :CustomerProfileFromOrderInd, 'A'
199
+ end
200
+ xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || 'NO'
139
201
  end
140
202
  end
141
-
203
+
142
204
  def add_soft_descriptors(xml, soft_desc)
143
- xml.tag! :SDMerchantName, soft_desc.merchant_name
144
- xml.tag! :SDProductDescription, soft_desc.product_description
145
- xml.tag! :SDMerchantCity, soft_desc.merchant_city
146
- xml.tag! :SDMerchantPhone, soft_desc.merchant_phone
147
- xml.tag! :SDMerchantURL, soft_desc.merchant_url
148
- xml.tag! :SDMerchantEmail, soft_desc.merchant_email
205
+ xml.tag! :SDMerchantName, soft_desc.merchant_name if soft_desc.merchant_name
206
+ xml.tag! :SDProductDescription, soft_desc.product_description if soft_desc.product_description
207
+ xml.tag! :SDMerchantCity, soft_desc.merchant_city if soft_desc.merchant_city
208
+ xml.tag! :SDMerchantPhone, soft_desc.merchant_phone if soft_desc.merchant_phone
209
+ xml.tag! :SDMerchantURL, soft_desc.merchant_url if soft_desc.merchant_url
210
+ xml.tag! :SDMerchantEmail, soft_desc.merchant_email if soft_desc.merchant_email
149
211
  end
150
212
 
151
213
  def add_address(xml, creditcard, options)
@@ -165,24 +227,48 @@ module ActiveMerchant #:nodoc:
165
227
  end
166
228
  end
167
229
 
230
+ # For Profile requests
231
+ def add_customer_address(xml, options)
232
+ if address = options[:billing_address] || options[:address]
233
+ xml.tag! :CustomerAddress1, address[:address1]
234
+ xml.tag! :CustomerAddress2, address[:address2]
235
+ xml.tag! :CustomerCity, address[:city]
236
+ xml.tag! :CustomerState, address[:state]
237
+ xml.tag! :CustomerZIP, address[:zip]
238
+ xml.tag! :CustomerPhone, address[:phone] ? address[:phone].scan(/\d/).to_s : nil
239
+ xml.tag! :CustomerCountryCode, address[:country]
240
+ end
241
+ end
242
+
168
243
  def add_creditcard(xml, creditcard, currency=nil)
169
244
  xml.tag! :AccountNum, creditcard.number
170
245
  xml.tag! :Exp, expiry_date(creditcard)
171
-
246
+
172
247
  xml.tag! :CurrencyCode, currency_code(currency)
173
248
  xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
174
-
175
- xml.tag! :CardSecValInd, 1 if creditcard.verification_value? && %w( visa discover ).include?(creditcard.brand)
249
+
250
+ # If you are trying to collect a Card Verification Number
251
+ # (CardSecVal) for a Visa or Discover transaction, pass one of these values:
252
+ # 1 Value is Present
253
+ # 2 Value on card but illegible
254
+ # 9 Cardholder states data not available
255
+ # If the transaction is not a Visa or Discover transaction:
256
+ # Null-fill this attribute OR
257
+ # Do not submit the attribute at all.
258
+ # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
259
+ if %w( visa discover ).include?(creditcard.brand)
260
+ xml.tag! :CardSecValInd, creditcard.verification_value? ? '1' : '9'
261
+ end
176
262
  xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value?
177
263
  end
178
-
264
+
179
265
  def add_refund(xml, currency=nil)
180
266
  xml.tag! :AccountNum, nil
181
-
267
+
182
268
  xml.tag! :CurrencyCode, currency_code(currency)
183
269
  xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
184
270
  end
185
-
271
+
186
272
 
187
273
  def parse(body)
188
274
  response = {}
@@ -195,8 +281,8 @@ module ActiveMerchant #:nodoc:
195
281
  end
196
282
  end
197
283
  response
198
- end
199
-
284
+ end
285
+
200
286
  def recurring_parse_element(response, node)
201
287
  if node.has_elements?
202
288
  node.elements.each{|e| recurring_parse_element(response, e) }
@@ -204,23 +290,24 @@ module ActiveMerchant #:nodoc:
204
290
  response[node.name.underscore.to_sym] = node.text
205
291
  end
206
292
  end
207
-
293
+
208
294
  def commit(order)
209
295
  headers = POST_HEADERS.merge("Content-length" => order.size.to_s)
210
296
  request = lambda {return parse(ssl_post(remote_url, order, headers))}
211
-
297
+
212
298
  # Failover URL will be used in the event of a connection error
213
299
  begin response = request.call; rescue ConnectionError; retry end
214
-
300
+
215
301
  Response.new(success?(response), message_from(response), response,
216
- {:authorization => "#{response[:tx_ref_num]};#{response[:order_id]}",
217
- :test => self.test?,
218
- :avs_result => {:code => response[:avs_resp_code]},
219
- :cvv_result => response[:cvv2_resp_code]
302
+ {
303
+ :authorization => authorization_string(response[:tx_ref_num], response[:order_id]),
304
+ :test => self.test?,
305
+ :avs_result => {:code => response[:avs_resp_code]},
306
+ :cvv_result => response[:cvv2_resp_code]
220
307
  }
221
308
  )
222
309
  end
223
-
310
+
224
311
  def remote_url
225
312
  unless $!.class == ActiveMerchant::ConnectionError
226
313
  self.test? ? self.test_url : self.live_url
@@ -230,18 +317,20 @@ module ActiveMerchant #:nodoc:
230
317
  end
231
318
 
232
319
  def success?(response)
233
- if response[:message_type].nil? || response[:message_type] == "R"
320
+ if response[:message_type] == "R"
234
321
  response[:proc_status] == SUCCESS
322
+ elsif response[:customer_profile_action]
323
+ response[:profile_proc_status] == SUCCESS
235
324
  else
236
325
  response[:proc_status] == SUCCESS &&
237
- response[:resp_code] == APPROVED
326
+ response[:resp_code] == APPROVED
238
327
  end
239
328
  end
240
-
329
+
241
330
  def message_from(response)
242
- success?(response) ? 'APPROVED' : response[:resp_msg] || response[:status_msg]
331
+ response[:resp_msg] || response[:status_msg] || response[:customer_profile_message]
243
332
  end
244
-
333
+
245
334
  def ip_authentication?
246
335
  @options[:ip_authentication] == true
247
336
  end
@@ -255,30 +344,46 @@ module ActiveMerchant #:nodoc:
255
344
  xml.tag! :IndustryType, parameters[:industry_type] || "EC"
256
345
  xml.tag! :MessageType, action
257
346
  add_bin_merchant_and_terminal(xml, parameters)
258
-
347
+
259
348
  yield xml if block_given?
260
-
261
- xml.tag! :Comments, parameters[:comments] if parameters[:comments]
349
+
262
350
  xml.tag! :OrderID, format_order_id(parameters[:order_id])
263
351
  xml.tag! :Amount, amount(money)
264
-
352
+ xml.tag! :Comments, parameters[:comments] if parameters[:comments]
353
+
354
+ if parameters[:soft_descriptors].is_a?(OrbitalSoftDescriptors)
355
+ add_soft_descriptors(xml, parameters[:soft_descriptors])
356
+ end
357
+
358
+ set_recurring_ind(xml, parameters)
359
+
265
360
  # Append Transaction Reference Number at the end for Refund transactions
266
361
  if action == "R"
267
- tx_ref_num, _ = parameters[:authorization].split(';')
362
+ tx_ref_num, _ = split_authorization(parameters[:authorization])
268
363
  xml.tag! :TxRefNum, tx_ref_num
269
364
  end
270
365
  end
271
366
  end
272
367
  xml.target!
273
368
  end
274
-
369
+
370
+ # For Canadian transactions on PNS Tampa on New Order
371
+ # RF - First Recurring Transaction
372
+ # RS - Subsequent Recurring Transactions
373
+ def set_recurring_ind(xml, parameters)
374
+ if parameters[:recurring_ind]
375
+ raise "RecurringInd must be set to either \"RF\" or \"RS\"" unless %w(RF RS).include?(parameters[:recurring_ind])
376
+ xml.tag! :RecurringInd, parameters[:recurring_ind]
377
+ end
378
+ end
379
+
275
380
  def build_mark_for_capture_xml(money, authorization, parameters = {})
276
- tx_ref_num, order_id = authorization.split(';')
381
+ tx_ref_num, order_id = split_authorization(authorization)
277
382
  xml = xml_envelope
278
383
  xml.tag! :Request do
279
384
  xml.tag! :MarkForCapture do
280
385
  add_xml_credentials(xml)
281
- xml.tag! :OrderID, order_id
386
+ xml.tag! :OrderID, format_order_id(order_id)
282
387
  xml.tag! :Amount, amount(money)
283
388
  add_bin_merchant_and_terminal(xml, parameters)
284
389
  xml.tag! :TxRefNum, tx_ref_num
@@ -286,26 +391,27 @@ module ActiveMerchant #:nodoc:
286
391
  end
287
392
  xml.target!
288
393
  end
289
-
290
- def build_void_request_xml(authorization, parameters = {})
291
- tx_ref_num, order_id = authorization.split(';')
394
+
395
+ def build_void_request_xml(money, authorization, parameters = {})
396
+ tx_ref_num, order_id = split_authorization(authorization)
292
397
  xml = xml_envelope
293
398
  xml.tag! :Request do
294
399
  xml.tag! :Reversal do
295
400
  add_xml_credentials(xml)
296
401
  xml.tag! :TxRefNum, tx_ref_num
297
402
  xml.tag! :TxRefIdx, parameters[:transaction_index]
298
- xml.tag! :OrderID, order_id
403
+ xml.tag! :AdjustedAmt, amount(money)
404
+ xml.tag! :OrderID, format_order_id(order_id || parameters[:order_id])
299
405
  add_bin_merchant_and_terminal(xml, parameters)
300
406
  end
301
407
  end
302
408
  xml.target!
303
409
  end
304
-
410
+
305
411
  def currency_code(currency)
306
412
  CURRENCY_CODES[(currency || self.default_currency)].to_s
307
413
  end
308
-
414
+
309
415
  def expiry_date(credit_card)
310
416
  "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
311
417
  end
@@ -346,6 +452,41 @@ module ActiveMerchant #:nodoc:
346
452
  order_id.gsub!(illegal_characters, '')
347
453
  order_id[0...22]
348
454
  end
455
+
456
+ def build_customer_request_xml(creditcard, options = {})
457
+ xml = xml_envelope
458
+ xml.tag! :Request do
459
+ xml.tag! :Profile do
460
+ xml.tag! :OrbitalConnectionUsername, @options[:login] unless ip_authentication?
461
+ xml.tag! :OrbitalConnectionPassword, @options[:password] unless ip_authentication?
462
+ xml.tag! :CustomerBin, bin
463
+ xml.tag! :CustomerMerchantID, @options[:merchant_id]
464
+ xml.tag! :CustomerName, creditcard.name if creditcard
465
+ xml.tag! :CustomerRefNum, options[:customer_ref_num] if options[:customer_ref_num]
466
+
467
+ add_customer_address(xml, options)
468
+
469
+ xml.tag! :CustomerProfileAction, options[:customer_profile_action] # C, R, U, D
470
+ xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || 'NO'
471
+
472
+ if options[:customer_profile_action] == 'C'
473
+ xml.tag! :CustomerProfileFromOrderInd, options[:customer_ref_num] ? 'S' : 'A'
474
+ end
475
+
476
+ xml.tag! :OrderDefaultDescription, options[:order_default_description][0..63] if options[:order_default_description]
477
+ xml.tag! :OrderDefaultAmount, options[:order_default_amount] if options[:order_default_amount]
478
+
479
+ if ['C', 'U'].include? options[:customer_profile_action]
480
+ xml.tag! :CustomerAccountType, 'CC' # Only credit card supported
481
+ xml.tag! :Status, options[:status] || 'A' # Active
482
+ end
483
+
484
+ xml.tag! :CCAccountNum, creditcard.number if creditcard
485
+ xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime("%m%y") if creditcard
486
+ end
487
+ end
488
+ xml.target!
489
+ end
349
490
  end
350
491
  end
351
492
  end