activemerchant 1.27.0 → 1.28.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.
@@ -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