activemerchant 1.56.0 → 1.57.0

Sign up to get free protection for your applications and to get access to all the features.
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