activemerchant 1.56.0 → 1.57.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +42 -0
  3. data/lib/active_merchant/billing/gateways/authorize_net.rb +52 -21
  4. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +1 -0
  5. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +243 -0
  6. data/lib/active_merchant/billing/gateways/bpoint.rb +1 -1
  7. data/lib/active_merchant/billing/gateways/braintree_blue.rb +11 -11
  8. data/lib/active_merchant/billing/gateways/bridge_pay.rb +37 -8
  9. data/lib/active_merchant/billing/gateways/card_stream.rb +36 -11
  10. data/lib/active_merchant/billing/gateways/checkout_v2.rb +3 -0
  11. data/lib/active_merchant/billing/gateways/clearhaus.rb +2 -2
  12. data/lib/active_merchant/billing/gateways/creditcall.rb +1 -1
  13. data/lib/active_merchant/billing/gateways/cyber_source.rb +12 -1
  14. data/lib/active_merchant/billing/gateways/element.rb +335 -0
  15. data/lib/active_merchant/billing/gateways/forte.rb +8 -0
  16. data/lib/active_merchant/billing/gateways/in_context_paypal_express.rb +15 -0
  17. data/lib/active_merchant/billing/gateways/litle.rb +1 -1
  18. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +2 -1
  19. data/lib/active_merchant/billing/gateways/ncr_secure_pay.rb +165 -0
  20. data/lib/active_merchant/billing/gateways/payeezy.rb +54 -12
  21. data/lib/active_merchant/billing/gateways/sage.rb +379 -128
  22. data/lib/active_merchant/billing/gateways/stripe.rb +13 -3
  23. data/lib/active_merchant/billing/gateways/trans_first.rb +26 -6
  24. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +573 -0
  25. data/lib/active_merchant/billing/gateways/worldpay.rb +7 -0
  26. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +11 -0
  27. data/lib/active_merchant/version.rb +1 -1
  28. metadata +7 -6
  29. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +0 -89
  30. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +0 -115
  31. data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +0 -149
  32. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +0 -97
@@ -23,6 +23,7 @@ module ActiveMerchant #:nodoc:
23
23
  def purchase(money, payment_method, options={})
24
24
  post = {}
25
25
  add_amount(post, money, options)
26
+ add_invoice(post, options)
26
27
  add_payment_method(post, payment_method)
27
28
  add_billing_address(post, payment_method, options)
28
29
  add_shipping_address(post, options)
@@ -34,6 +35,7 @@ module ActiveMerchant #:nodoc:
34
35
  def authorize(money, payment_method, options={})
35
36
  post = {}
36
37
  add_amount(post, money, options)
38
+ add_invoice(post, options)
37
39
  add_payment_method(post, payment_method)
38
40
  add_billing_address(post, payment_method, options)
39
41
  add_shipping_address(post, options)
@@ -44,6 +46,7 @@ module ActiveMerchant #:nodoc:
44
46
 
45
47
  def capture(money, authorization, options={})
46
48
  post = {}
49
+ add_invoice(post, options)
47
50
  post[:transaction_id] = transaction_id_from(authorization)
48
51
  post[:authorization_code] = authorization_code_from(authorization)
49
52
  post[:action] = "capture"
@@ -54,6 +57,7 @@ module ActiveMerchant #:nodoc:
54
57
  def credit(money, payment_method, options={})
55
58
  post = {}
56
59
  add_amount(post, money, options)
60
+ add_invoice(post, options)
57
61
  add_payment_method(post, payment_method)
58
62
  add_billing_address(post, payment_method, options)
59
63
  post[:action] = "disburse"
@@ -95,6 +99,10 @@ module ActiveMerchant #:nodoc:
95
99
  post[:location_id] = "loc_#{@options[:location_id]}"
96
100
  end
97
101
 
102
+ def add_invoice(post, options)
103
+ post[:order_number] = options[:order_id]
104
+ end
105
+
98
106
  def add_amount(post, money, options)
99
107
  post[:authorization_amount] = amount(money)
100
108
  end
@@ -0,0 +1,15 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class InContextPaypalExpressGateway < PaypalExpressGateway
4
+ self.test_redirect_url = 'https://www.sandbox.paypal.com/checkoutnow'
5
+ self.live_redirect_url = 'https://www.paypal.com/checkoutnow'
6
+
7
+ def redirect_url_for(token, options = {})
8
+ options = {review: true}.update(options)
9
+ url = "#{redirect_url}?token=#{token}"
10
+ url += '&useraction=commit' unless options[:review]
11
+ url
12
+ end
13
+ end
14
+ end
15
+ end
@@ -243,7 +243,7 @@ module ActiveMerchant #:nodoc:
243
243
  def add_order_source(doc, payment_method, options)
244
244
  if options[:order_source]
245
245
  doc.orderSource(options[:order_source])
246
- elsif payment_method.is_a?(NetworkTokenizationCreditCard)
246
+ elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay
247
247
  doc.orderSource('applepay')
248
248
  elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present?
249
249
  doc.orderSource('retail')
@@ -99,7 +99,8 @@ module ActiveMerchant #:nodoc:
99
99
 
100
100
  def add_invoice(post, options)
101
101
  if options.has_key? :order_id
102
- post[:invoice_number] = options[:order_id].to_s.gsub(/[^\w.]/, '')
102
+ order_id = options[:order_id].to_s.gsub(/[^\w.]/, '')
103
+ post[:invoice_number] = truncate(order_id, 17)
103
104
  end
104
105
  end
105
106
 
@@ -0,0 +1,165 @@
1
+ require 'nokogiri'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class NcrSecurePayGateway < Gateway
6
+ self.test_url = 'https://testbox.monetra.com:8665/'
7
+ self.live_url = 'https://ps.ncrsecurepay.com:8444/'
8
+
9
+ self.supported_countries = ['US']
10
+ self.default_currency = 'USD'
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
12
+
13
+ self.homepage_url = 'http://www.ncrretailonline.com'
14
+ self.display_name = 'NCR Secure Pay'
15
+
16
+ def initialize(options={})
17
+ requires!(options, :username, :password)
18
+ super
19
+ end
20
+
21
+ def purchase(money, payment, options={})
22
+ post = {}
23
+ add_invoice(post, money, options)
24
+ add_payment(post, payment)
25
+ add_address(post, payment, options)
26
+
27
+ commit('sale', post)
28
+ end
29
+
30
+ def authorize(money, payment, options={})
31
+ post = {}
32
+ add_invoice(post, money, options)
33
+ add_payment(post, payment)
34
+ add_address(post, payment, options)
35
+
36
+ commit('preauth', post)
37
+ end
38
+
39
+ def capture(money, authorization, options={})
40
+ post = {}
41
+ add_reference(post, authorization)
42
+ add_invoice(post, money, options)
43
+
44
+ commit('preauthcomplete', post)
45
+ end
46
+
47
+ def refund(money, authorization, options={})
48
+ post = {}
49
+ add_reference(post, authorization)
50
+ add_invoice(post, money, options)
51
+
52
+ commit('credit', post)
53
+ end
54
+
55
+ def void(authorization, options={})
56
+ post = {}
57
+ add_reference(post, authorization)
58
+ commit('void', post)
59
+ end
60
+
61
+ def verify(credit_card, options={})
62
+ MultiResponse.run(:use_first_response) do |r|
63
+ r.process { authorize(100, credit_card, options) }
64
+ r.process(:ignore_result) { void(r.authorization, options) }
65
+ end
66
+ end
67
+
68
+ def supports_scrubbing?
69
+ true
70
+ end
71
+
72
+ def scrub(transcript)
73
+ transcript.gsub(%r((<password>)[^<]*(</password>))i, '\1[FILTERED]\2').
74
+ gsub(%r((<account>)[^<]*(</account>))i, '\1[FILTERED]\2').
75
+ gsub(%r((<cv>)[^<]*(</cv>))i, '\1[FILTERED]\2')
76
+ end
77
+
78
+ private
79
+
80
+ def add_reference(post, reference)
81
+ post[:ttid] = reference
82
+ end
83
+
84
+ def add_address(post, payment, options)
85
+ address = options[:billing_address] || options[:address]
86
+ post[:zip] = address[:zip]
87
+ post[:street] = address[:address1]
88
+ end
89
+
90
+ def add_invoice(post, money, options)
91
+ post[:amount] = amount(money)
92
+ post[:currency] = (options[:currency] || currency(money))
93
+ post[:descmerch] = options[:merchant] if options[:merchant]
94
+ post[:ordernum] = options[:order_id] if options[:order_id]
95
+ post[:comments] = options[:description] if options[:description]
96
+ end
97
+
98
+ def add_payment(post, payment)
99
+ post[:cardholdername] = payment.name
100
+ post[:account] = payment.number
101
+ post[:cv] = payment.verification_value
102
+ post[:expdate] = expdate(payment)
103
+ end
104
+
105
+ def parse(body)
106
+ doc = Nokogiri::XML(body)
107
+ doc.remove_namespaces!
108
+ response = doc.xpath("/MonetraResp/Resp")[0]
109
+ resp_params = {}
110
+
111
+ response.elements.each do |node|
112
+ resp_params[node.name.downcase.to_sym] = node.text
113
+ end
114
+ resp_params
115
+ end
116
+
117
+ def commit(action, parameters)
118
+ url = (test? ? test_url : live_url)
119
+ response = parse(ssl_post(url, request_body(action, parameters)))
120
+
121
+ Response.new(
122
+ success_from(response),
123
+ message_from(response),
124
+ response,
125
+ authorization: authorization_from(response),
126
+ test: test?,
127
+ error_code: error_code_from(response)
128
+ )
129
+ end
130
+
131
+ def success_from(response)
132
+ response[:code] == "AUTH"
133
+ end
134
+
135
+ def message_from(response)
136
+ response[:verbiage]
137
+ end
138
+
139
+ def authorization_from(response)
140
+ response[:ttid]
141
+ end
142
+
143
+ def request_body(action, parameters = {})
144
+ Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml|
145
+ xml.MonetraTrans do
146
+ xml.Trans(identifier: parameters.delete(:identifier) || "1") do
147
+ xml.username(options[:username])
148
+ xml.password(options[:password])
149
+ xml.action(action)
150
+ parameters.each do |name, value|
151
+ xml.send(name, value)
152
+ end
153
+ end
154
+ end
155
+ end.to_xml
156
+ end
157
+
158
+ def error_code_from(response)
159
+ unless success_from(response)
160
+ response[:msoft_code] || response[:phard_code]
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -30,22 +30,22 @@ module ActiveMerchant
30
30
  super
31
31
  end
32
32
 
33
- def purchase(amount, creditcard, options = {})
33
+ def purchase(amount, payment_method, options = {})
34
34
  params = {transaction_type: 'purchase'}
35
35
 
36
36
  add_invoice(params, options)
37
- add_creditcard(params, creditcard)
37
+ add_payment_method(params, payment_method)
38
38
  add_address(params, options)
39
39
  add_amount(params, amount, options)
40
40
 
41
41
  commit(params, options)
42
42
  end
43
43
 
44
- def authorize(amount, creditcard, options = {})
44
+ def authorize(amount, payment_method, options = {})
45
45
  params = {transaction_type: 'authorize'}
46
46
 
47
47
  add_invoice(params, options)
48
- add_creditcard(params, creditcard)
48
+ add_payment_method(params, payment_method)
49
49
  add_address(params, options)
50
50
  add_amount(params, amount, options)
51
51
 
@@ -71,7 +71,7 @@ module ActiveMerchant
71
71
  end
72
72
 
73
73
  def void(authorization, options = {})
74
- params = {transaction_type: 'refund'}
74
+ params = {transaction_type: 'void'}
75
75
 
76
76
  add_authorization_info(params, authorization)
77
77
  add_amount(params, amount_from_authorization(authorization), options)
@@ -79,6 +79,26 @@ module ActiveMerchant
79
79
  commit(params, options)
80
80
  end
81
81
 
82
+ def verify(credit_card, options={})
83
+ MultiResponse.run(:use_first_response) do |r|
84
+ r.process { authorize(100, credit_card, options) }
85
+ r.process(:ignore_result) { void(r.authorization, options) }
86
+ end
87
+ end
88
+
89
+ def supports_scrubbing?
90
+ true
91
+ end
92
+
93
+ def scrub(transcript)
94
+ transcript.
95
+ gsub(%r((Token: )(\w|-)+), '\1[FILTERED]').
96
+ gsub(%r((\\?"card_number\\?":\\?")\d+), '\1[FILTERED]').
97
+ gsub(%r((\\?"cvv\\?":\\?")\d+), '\1[FILTERED]').
98
+ gsub(%r((\\?"account_number\\?":\\?")\d+), '\1[FILTERED]').
99
+ gsub(%r((\\?"routing_number\\?":\\?")\d+), '\1[FILTERED]')
100
+ end
101
+
82
102
  private
83
103
 
84
104
  def add_invoice(params, options)
@@ -96,6 +116,14 @@ module ActiveMerchant
96
116
  params[:method] = method
97
117
  end
98
118
 
119
+ def add_payment_method(params, payment_method)
120
+ if payment_method.is_a? Check
121
+ add_echeck(params, payment_method)
122
+ else
123
+ add_creditcard(params, payment_method)
124
+ end
125
+ end
126
+
99
127
  def add_creditcard(params, creditcard)
100
128
  credit_card = {}
101
129
 
@@ -109,6 +137,19 @@ module ActiveMerchant
109
137
  params[:credit_card] = credit_card
110
138
  end
111
139
 
140
+ def add_echeck(params, echeck)
141
+ tele_check = {}
142
+
143
+ tele_check[:check_number] = echeck.number
144
+ tele_check[:check_type] = "P"
145
+ tele_check[:routing_number] = echeck.routing_number
146
+ tele_check[:account_number] = echeck.account_number
147
+ tele_check[:accountholder_name] = "#{echeck.first_name} #{echeck.last_name}"
148
+
149
+ params[:method] = 'tele_check'
150
+ params[:tele_check] = tele_check
151
+ end
152
+
112
153
  def add_address(params, options)
113
154
  address = options[:billing_address]
114
155
  return unless address
@@ -141,12 +182,9 @@ module ActiveMerchant
141
182
  url = "#{url}/#{transaction_id}"
142
183
  end
143
184
 
144
- success = false
145
185
  begin
146
186
  body = params.to_json
147
- raw_response = ssl_post(url, body, headers(body))
148
- response = parse(raw_response)
149
- success = (response['transaction_status'] == 'approved')
187
+ response = parse(ssl_post(url, body, headers(body)))
150
188
  rescue ResponseError => e
151
189
  response = response_error(e.response.body)
152
190
  rescue JSON::ParserError
@@ -154,17 +192,21 @@ module ActiveMerchant
154
192
  end
155
193
 
156
194
  Response.new(
157
- success,
158
- handle_message(response, success),
195
+ success_from(response),
196
+ handle_message(response, success_from(response)),
159
197
  response,
160
198
  test: test?,
161
199
  authorization: authorization_from(params, response),
162
200
  avs_result: {code: response['avs']},
163
201
  cvv_result: response['cvv2'],
164
- error_code: error_code(response, success)
202
+ error_code: error_code(response, success_from(response))
165
203
  )
166
204
  end
167
205
 
206
+ def success_from(response)
207
+ response['transaction_status'] == 'approved'
208
+ end
209
+
168
210
  def authorization_from(params, response)
169
211
  [
170
212
  response['transaction_id'],
@@ -1,172 +1,423 @@
1
- require 'active_merchant/billing/gateways/sage/sage_bankcard'
2
- require 'active_merchant/billing/gateways/sage/sage_virtual_check'
3
- require 'active_merchant/billing/gateways/sage/sage_vault'
4
-
5
1
  module ActiveMerchant #:nodoc:
6
2
  module Billing #:nodoc:
7
3
  class SageGateway < Gateway
8
- self.supported_countries = SageBankcardGateway.supported_countries
9
- self.supported_cardtypes = SageBankcardGateway.supported_cardtypes
10
-
11
- self.abstract_class = true
12
-
13
- # Creates a new SageGateway
14
- #
15
- # The gateway requires that a valid login and password be passed
16
- # in the +options+ hash.
17
- #
18
- # ==== Options
19
- #
20
- # * <tt>:login</tt> - The Sage Payment Solutions Merchant ID Number.
21
- # * <tt>:password</tt> - The Sage Payment Solutions Merchant Key Number.
4
+ self.display_name = 'http://www.sagepayments.com'
5
+ self.homepage_url = 'Sage Payment Solutions'
6
+ self.live_url = 'https://www.sagepayments.net/cgi-bin'
7
+
8
+ self.supported_countries = ['US', 'CA']
9
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
10
+
11
+ TRANSACTIONS = {
12
+ :purchase => '01',
13
+ :authorization => '02',
14
+ :capture => '11',
15
+ :void => '04',
16
+ :credit => '06',
17
+ :refund => '10'
18
+ }
19
+
20
+ SOURCE_CARD = "bankcard"
21
+ SOURCE_ECHECK = "virtual_check"
22
+
22
23
  def initialize(options = {})
23
24
  requires!(options, :login, :password)
24
25
  super
25
26
  end
26
27
 
27
- # Performs an authorization transaction
28
- #
29
- # ==== Parameters
30
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
31
- # * <tt>credit_card</tt> - The CreditCard object to be used as the funding source for the transaction.
32
- # * <tt>options</tt> - A hash of optional parameters.
33
- # * <tt>:order_id</tt> - A unique reference for this order. (maximum of 20 characters).
34
- # * <tt>:email</tt> - The customer's email address
35
- # * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
36
- # * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
37
- # * <tt>:address1</tt> - The billing address street
38
- # * <tt>:city</tt> - The billing address city
39
- # * <tt>:state</tt> - The billing address state
40
- # * <tt>:country</tt> - The 2 digit ISO billing address country code
41
- # * <tt>:zip</tt> - The billing address zip code
42
- # * <tt>:phone</tt> - The billing address phone number
43
- # * <tt>:fax</tt> - The billing address fax number
44
- # * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
45
- # * <tt>:name</tt> - The name at the shipping address
46
- # * <tt>:address1</tt> - The shipping address street
47
- # * <tt>:city</tt> - The shipping address city
48
- # * <tt>:state</tt> - The shipping address state code
49
- # * <tt>:country</tt> - The 2 digit ISO shipping address country code
50
- # * <tt>:zip</tt> - The shipping address zip code
51
- # * <tt>:tax</tt> - The tax amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_tax</tt>.
52
- # * <tt>:shipping</tt> - The shipping amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_shipping</tt>.
53
28
  def authorize(money, credit_card, options = {})
54
- bankcard.authorize(money, credit_card, options)
55
- end
56
-
57
- # Performs a purchase, which is essentially an authorization and capture in a single operation.
58
- #
59
- # ==== Parameters
60
- #
61
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
62
- # * <tt>source</tt> - The CreditCard or Check object to be used as the funding source for the transaction.
63
- # * <tt>options</tt> - A hash of optional parameters.
64
- # * <tt>:order_id</tt> - A unique reference for this order. (maximum of 20 characters).
65
- # * <tt>:email</tt> - The customer's email address
66
- # * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
67
- # * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
68
- # * <tt>:address1</tt> - The billing address street
69
- # * <tt>:city</tt> - The billing address city
70
- # * <tt>:state</tt> - The billing address state
71
- # * <tt>:country</tt> - The 2 digit ISO billing address country code
72
- # * <tt>:zip</tt> - The billing address zip code
73
- # * <tt>:phone</tt> - The billing address phone number
74
- # * <tt>:fax</tt> - The billing address fax number
75
- # * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
76
- # * <tt>:name</tt> - The name at the shipping address
77
- # * <tt>:address1</tt> - The shipping address street
78
- # * <tt>:city</tt> - The shipping address city
79
- # * <tt>:state</tt> - The shipping address state code
80
- # * <tt>:country</tt> - The 2 digit ISO shipping address country code
81
- # * <tt>:zip</tt> - The shipping address zip code
82
- # * <tt>:tax</tt> - The tax amount for the transaction as an integer value in cents. Maps to Sage <tt>T_tax</tt>.
83
- # * <tt>:shipping</tt> - The shipping amount for the transaction as an integer value in cents. Maps to Sage <tt>T_shipping</tt>.
84
- #
85
- # ==== Additional options in the +options+ hash for when using a Check as the funding source
86
- # * <tt>:originator_id</tt> - 10 digit originator. If not provided, Sage will use the default Originator ID for the specific customer type.
87
- # * <tt>:addenda</tt> - Transaction addenda.
88
- # * <tt>:ssn</tt> - The customer's Social Security Number.
89
- # * <tt>:drivers_license_state</tt> - The customer's drivers license state code.
90
- # * <tt>:drivers_license_number</tt> - The customer's drivers license number.
91
- # * <tt>:date_of_birth</tt> - The customer's date of birth as a Time or Date object or a string in the format <tt>mm/dd/yyyy</tt>.
92
- def purchase(money, source, options = {})
93
- if card_brand(source) == "check"
94
- virtual_check.purchase(money, source, options)
29
+ post = {}
30
+ add_credit_card(post, credit_card)
31
+ add_transaction_data(post, money, options)
32
+ commit(:authorization, post, SOURCE_CARD)
33
+ end
34
+
35
+ def purchase(money, payment_method, options = {})
36
+ post = {}
37
+ if card_brand(payment_method) == "check"
38
+ source = SOURCE_ECHECK
39
+ add_check(post, payment_method)
40
+ add_check_customer_data(post, options)
95
41
  else
96
- bankcard.purchase(money, source, options)
42
+ source = SOURCE_CARD
43
+ add_credit_card(post, payment_method)
97
44
  end
45
+ add_transaction_data(post, money, options)
46
+ commit(:purchase, post, source)
98
47
  end
99
48
 
100
- # Captures authorized funds.
101
- #
102
- # ==== Parameters
103
- #
104
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents. Sage doesn't support changing the capture amount, so the full amount of the initial transaction will be captured.
105
- # * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
49
+ # The +money+ amount is not used. The entire amount of the
50
+ # initial authorization will be captured.
106
51
  def capture(money, reference, options = {})
107
- bankcard.capture(money, reference, options)
52
+ post = {}
53
+ add_reference(post, reference)
54
+ commit(:capture, post, SOURCE_CARD)
108
55
  end
109
56
 
110
- # Voids a prior transaction. Works for both CreditCard and Check transactions.
111
- #
112
- # ==== Parameters
113
- #
114
- # * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
115
57
  def void(reference, options = {})
116
- if reference.split(";").last == "virtual_check"
117
- virtual_check.void(reference, options)
118
- else
119
- bankcard.void(reference, options)
120
- end
58
+ post = {}
59
+ add_reference(post, reference)
60
+ source = reference.split(";").last
61
+ commit(:void, post, source)
121
62
  end
122
63
 
123
- #
124
- # ==== Parameters
125
- #
126
- # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
127
- # * <tt>source</tt> - The CreditCard or Check object to be used as the target for the credit.
128
- def credit(money, source, options = {})
129
- if card_brand(source) == "check"
130
- virtual_check.credit(money, source, options)
64
+ def credit(money, payment_method, options = {})
65
+ post = {}
66
+ if card_brand(payment_method) == "check"
67
+ source = SOURCE_ECHECK
68
+ add_check(post, payment_method)
69
+ add_check_customer_data(post, options)
131
70
  else
132
- bankcard.credit(money, source, options)
71
+ source = SOURCE_CARD
72
+ add_credit_card(post, payment_method)
133
73
  end
74
+ add_transaction_data(post, money, options)
75
+ commit(:credit, post, source)
134
76
  end
135
77
 
136
78
  def refund(money, reference, options={})
137
- bankcard.refund(money, reference, options)
79
+ post = {}
80
+ add_reference(post, reference)
81
+ add_transaction_data(post, money, options)
82
+ commit(:refund, post, SOURCE_CARD)
138
83
  end
139
84
 
140
- # Stores a credit card in the Sage vault.
141
- #
142
- # ==== Parameters
143
- #
144
- # * <tt>credit_card</tt> - The CreditCard object to be stored.
145
85
  def store(credit_card, options = {})
146
86
  vault.store(credit_card, options)
147
87
  end
148
88
 
149
- # Deletes a stored card from the Sage vault.
150
- #
151
- # ==== Parameters
152
- #
153
- # * <tt>identification</tt> - The 'GUID' identifying the stored card.
154
89
  def unstore(identification, options = {})
155
90
  vault.unstore(identification, options)
156
91
  end
157
92
 
158
93
  private
159
94
 
160
- def bankcard
161
- @bankcard ||= SageBankcardGateway.new(@options)
95
+ def add_credit_card(post, credit_card)
96
+ post[:C_name] = credit_card.name
97
+ post[:C_cardnumber] = credit_card.number
98
+ post[:C_exp] = expdate(credit_card)
99
+ post[:C_cvv] = credit_card.verification_value if credit_card.verification_value?
100
+ end
101
+
102
+ def add_check(post, check)
103
+ post[:C_first_name] = check.first_name
104
+ post[:C_last_name] = check.last_name
105
+ post[:C_rte] = check.routing_number
106
+ post[:C_acct] = check.account_number
107
+ post[:C_check_number] = check.number
108
+ post[:C_acct_type] = account_type(check)
109
+ end
110
+
111
+ def add_check_customer_data(post, options)
112
+ # Required  Customer Type – (NACHA Transaction Class)
113
+ # CCD for Commercial, Merchant Initiated
114
+ # PPD for Personal, Merchant Initiated
115
+ # WEB for Internet, Consumer Initiated
116
+ # RCK for Returned Checks
117
+ # ARC for Account Receivable Entry
118
+ # TEL for TelephoneInitiated
119
+ post[:C_customer_type] = "WEB"
120
+
121
+ # Optional  10  Digit Originator  ID – Assigned  By for  each transaction  class  or  business  purpose. If  not provided, the default Originator ID for the specific  Customer Type will be applied. 
122
+ post[:C_originator_id] = options[:originator_id]
123
+
124
+ # Optional  Transaction Addenda
125
+ post[:T_addenda] = options[:addenda]
126
+
127
+ # Required  Check  Writer  Social  Security  Number  (  Numbers Only, No Dashes ) 
128
+ post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '')
129
+
130
+ post[:C_dl_state_code] = options[:drivers_license_state]
131
+ post[:C_dl_number] = options[:drivers_license_number]
132
+ post[:C_dob] = format_birth_date(options[:date_of_birth])
162
133
  end
163
134
 
164
- def virtual_check
165
- @virtual_check ||= SageVirtualCheckGateway.new(@options)
135
+ def format_birth_date(date)
136
+ date.respond_to?(:strftime) ? date.strftime("%m/%d/%Y") : date
137
+ end
138
+
139
+ # DDA for Checking
140
+ # SAV for Savings 
141
+ def account_type(check)
142
+ case check.account_type
143
+ when 'checking' then 'DDA'
144
+ when 'savings' then 'SAV'
145
+ else raise ArgumentError, "Unknown account type #{check.account_type}"
146
+ end
147
+ end
148
+
149
+ def parse(data, source)
150
+ source == SOURCE_ECHECK ? parse_check(data) : parse_credit_card(data)
151
+ end
152
+
153
+ def parse_check(data)
154
+ response = {}
155
+ response[:success] = data[1,1]
156
+ response[:code] = data[2,6].strip
157
+ response[:message] = data[8,32].strip
158
+ response[:risk] = data[40, 2]
159
+ response[:reference] = data[42, 10]
160
+
161
+ extra_data = data[53...-1].split("\034")
162
+ response[:order_number] = extra_data[0]
163
+ response[:authentication_indicator] = extra_data[1]
164
+ response[:authentication_disclosure] = extra_data[2]
165
+ response
166
+ end
167
+
168
+ def parse_credit_card(data)
169
+ response = {}
170
+ response[:success] = data[1,1]
171
+ response[:code] = data[2,6]
172
+ response[:message] = data[8,32].strip
173
+ response[:front_end] = data[40, 2]
174
+ response[:cvv_result] = data[42, 1]
175
+ response[:avs_result] = data[43, 1].strip
176
+ response[:risk] = data[44, 2]
177
+ response[:reference] = data[46, 10]
178
+
179
+ response[:order_number], response[:recurring] = data[57...-1].split("\034")
180
+ response
181
+ end
182
+
183
+ def add_invoice(post, options)
184
+ post[:T_ordernum] = (options[:order_id] || generate_unique_id).slice(0, 20)
185
+ post[:T_tax] = amount(options[:tax]) unless options[:tax].blank?
186
+ post[:T_shipping] = amount(options[:shipping]) unless options[:shipping].blank?
187
+ end
188
+
189
+ def add_reference(post, reference)
190
+ ref, _ = reference.to_s.split(";")
191
+ post[:T_reference] = ref
192
+ end
193
+
194
+ def add_amount(post, money)
195
+ post[:T_amt] = amount(money)
196
+ end
197
+
198
+ def add_customer_data(post, options)
199
+ post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil
200
+ end
201
+
202
+ def add_addresses(post, options)
203
+ billing_address = options[:billing_address] || options[:address] || {}
204
+
205
+ post[:C_address] = billing_address[:address1]
206
+ post[:C_city] = billing_address[:city]
207
+
208
+ if ['US', 'CA'].include?(billing_address[:country])
209
+ post[:C_state] = billing_address[:state]
210
+ else
211
+ post[:C_state] = "Outside of United States"
212
+ end
213
+
214
+ post[:C_zip] = billing_address[:zip]
215
+ post[:C_country] = billing_address[:country]
216
+ post[:C_telephone] = billing_address[:phone]
217
+ post[:C_fax] = billing_address[:fax]
218
+ post[:C_email] = options[:email]
219
+
220
+ if shipping_address = options[:shipping_address]
221
+ post[:C_ship_name] = shipping_address[:name]
222
+ post[:C_ship_address] = shipping_address[:address1]
223
+ post[:C_ship_city] = shipping_address[:city]
224
+ post[:C_ship_state] = shipping_address[:state]
225
+ post[:C_ship_zip] = shipping_address[:zip]
226
+ post[:C_ship_country] = shipping_address[:country]
227
+ end
228
+ end
229
+
230
+ def add_transaction_data(post, money, options)
231
+ add_amount(post, money)
232
+ add_invoice(post, options)
233
+ add_addresses(post, options)
234
+ add_customer_data(post, options)
235
+ end
236
+
237
+ def commit(action, params, source)
238
+ url = url(params, source)
239
+ response = parse(ssl_post(url, post_data(action, params)), source)
240
+
241
+ Response.new(success?(response), response[:message], response,
242
+ :test => test?,
243
+ :authorization => authorization_from(response, source),
244
+ :avs_result => { :code => response[:avs_result] },
245
+ :cvv_result => response[:cvv_result]
246
+ )
247
+ end
248
+
249
+ def url(params, source)
250
+ if source == SOURCE_ECHECK
251
+ "#{live_url}/eftVirtualCheck.dll?transaction"
252
+ else
253
+ "#{live_url}/eftBankcard.dll?transaction"
254
+ end
255
+ end
256
+
257
+ def authorization_from(response, source)
258
+ "#{response[:reference]};#{source}"
259
+ end
260
+
261
+ def success?(response)
262
+ response[:success] == 'A'
263
+ end
264
+
265
+ def post_data(action, params = {})
266
+ params[:M_id] = @options[:login]
267
+ params[:M_key] = @options[:password]
268
+ params[:T_code] = TRANSACTIONS[action]
269
+
270
+ params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
166
271
  end
167
272
 
168
273
  def vault
169
- @vault ||= SageVaultGateway.new(@options)
274
+ @vault ||= SageVault.new(@options, self)
275
+ end
276
+
277
+ class SageVault
278
+
279
+ def initialize(options, gateway)
280
+ @live_url = 'https://www.sagepayments.net/web_services/wsVault/wsVault.asmx'
281
+ @options = options
282
+ @gateway = gateway
283
+ end
284
+
285
+ def store(credit_card, options = {})
286
+ request = build_store_request(credit_card, options)
287
+ commit(:store, request)
288
+ end
289
+
290
+ def unstore(identification, options = {})
291
+ request = build_unstore_request(identification, options)
292
+ commit(:unstore, request)
293
+ end
294
+
295
+ private
296
+
297
+ # A valid request example, since the Sage docs have none:
298
+ #
299
+ # <?xml version="1.0" encoding="UTF-8" ?>
300
+ # <SOAP-ENV:Envelope
301
+ # xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
302
+ # xmlns:ns1="https://www.sagepayments.net/web_services/wsVault/wsVault">
303
+ # <SOAP-ENV:Body>
304
+ # <ns1:INSERT_CREDIT_CARD_DATA>
305
+ # <ns1:M_ID>279277516172</ns1:M_ID>
306
+ # <ns1:M_KEY>O3I8G2H8V6A3</ns1:M_KEY>
307
+ # <ns1:CARDNUMBER>4111111111111111</ns1:CARDNUMBER>
308
+ # <ns1:EXPIRATION_DATE>0915</ns1:EXPIRATION_DATE>
309
+ # </ns1:INSERT_CREDIT_CARD_DATA>
310
+ # </SOAP-ENV:Body>
311
+ # </SOAP-ENV:Envelope>
312
+ def build_store_request(credit_card, options)
313
+ xml = Builder::XmlMarkup.new
314
+ add_credit_card(xml, credit_card, options)
315
+ xml.target!
316
+ end
317
+
318
+ def build_unstore_request(identification, options)
319
+ xml = Builder::XmlMarkup.new
320
+ add_identification(xml, identification, options)
321
+ xml.target!
322
+ end
323
+
324
+ def add_customer_data(xml)
325
+ xml.tag! 'ns1:M_ID', @options[:login]
326
+ xml.tag! 'ns1:M_KEY', @options[:password]
327
+ end
328
+
329
+ def add_credit_card(xml, credit_card, options)
330
+ xml.tag! 'ns1:CARDNUMBER', credit_card.number
331
+ xml.tag! 'ns1:EXPIRATION_DATE', exp_date(credit_card)
332
+ end
333
+
334
+ def add_identification(xml, identification, options)
335
+ xml.tag! 'ns1:GUID', identification
336
+ end
337
+
338
+ def exp_date(credit_card)
339
+ year = sprintf("%.4i", credit_card.year)
340
+ month = sprintf("%.2i", credit_card.month)
341
+
342
+ "#{month}#{year[-2..-1]}"
343
+ end
344
+
345
+ def commit(action, request)
346
+ response = parse(@gateway.ssl_post(@live_url,
347
+ build_soap_request(action, request),
348
+ build_headers(action))
349
+ )
350
+
351
+ case action
352
+ when :store
353
+ success = response[:success] == 'true'
354
+ message = response[:message].downcase.capitalize if response[:message]
355
+ when :unstore
356
+ success = response[:delete_data_result] == 'true'
357
+ message = success ? 'Succeeded' : 'Failed'
358
+ end
359
+
360
+ Response.new(success, message, response,
361
+ authorization: response[:guid]
362
+ )
363
+ end
364
+
365
+ ENVELOPE_NAMESPACES = {
366
+ 'xmlns:SOAP-ENV' => "http://schemas.xmlsoap.org/soap/envelope/",
367
+ 'xmlns:ns1' => "https://www.sagepayments.net/web_services/wsVault/wsVault"
368
+ }
369
+
370
+ ACTION_ELEMENTS = {
371
+ store: 'INSERT_CREDIT_CARD_DATA',
372
+ unstore: 'DELETE_DATA'
373
+ }
374
+
375
+ def build_soap_request(action, body)
376
+ xml = Builder::XmlMarkup.new
377
+
378
+ xml.instruct!
379
+ xml.tag! 'SOAP-ENV:Envelope', ENVELOPE_NAMESPACES do
380
+ xml.tag! 'SOAP-ENV:Body' do
381
+ xml.tag! "ns1:#{ACTION_ELEMENTS[action]}" do
382
+ add_customer_data(xml)
383
+ xml << body
384
+ end
385
+ end
386
+ end
387
+ xml.target!
388
+ end
389
+
390
+ SOAP_ACTIONS = {
391
+ store: 'https://www.sagepayments.net/web_services/wsVault/wsVault/INSERT_CREDIT_CARD_DATA',
392
+ unstore: 'https://www.sagepayments.net/web_services/wsVault/wsVault/DELETE_DATA'
393
+ }
394
+
395
+ def build_headers(action)
396
+ {
397
+ "SOAPAction" => SOAP_ACTIONS[action],
398
+ "Content-Type" => "text/xml; charset=utf-8"
399
+ }
400
+ end
401
+
402
+ def parse(body)
403
+ response = {}
404
+ hashify_xml!(body, response)
405
+ response
406
+ end
407
+
408
+ def hashify_xml!(xml, response)
409
+ xml = REXML::Document.new(xml)
410
+
411
+ # Store
412
+ xml.elements.each("//Table1/*") do |node|
413
+ response[node.name.underscore.to_sym] = node.text
414
+ end
415
+
416
+ # Unstore
417
+ xml.elements.each("//DELETE_DATAResponse/*") do |node|
418
+ response[node.name.underscore.to_sym] = node.text
419
+ end
420
+ end
170
421
  end
171
422
  end
172
423
  end