activemerchant 1.18.1 → 1.20.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 (27) hide show
  1. data.tar.gz.sig +5 -0
  2. data/CHANGELOG +15 -0
  3. data/CONTRIBUTORS +4 -0
  4. data/lib/active_merchant.rb +1 -0
  5. data/lib/active_merchant/billing/credit_card.rb +1 -1
  6. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +5 -1
  7. data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -2
  8. data/lib/active_merchant/billing/gateways/elavon.rb +2 -1
  9. data/lib/active_merchant/billing/gateways/exact.rb +5 -0
  10. data/lib/active_merchant/billing/gateways/moneris.rb +4 -0
  11. data/lib/active_merchant/billing/gateways/ogone.rb +94 -56
  12. data/lib/active_merchant/billing/gateways/orbital.rb +34 -26
  13. data/lib/active_merchant/billing/gateways/pay_junction.rb +6 -1
  14. data/lib/active_merchant/billing/gateways/samurai.rb +120 -0
  15. data/lib/active_merchant/billing/gateways/skip_jack.rb +6 -1
  16. data/lib/active_merchant/billing/gateways/usa_epay.rb +13 -184
  17. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +1496 -0
  18. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +194 -0
  19. data/lib/active_merchant/billing/integrations/dwolla.rb +2 -2
  20. data/lib/active_merchant/billing/integrations/dwolla/helper.rb +7 -4
  21. data/lib/active_merchant/billing/integrations/dwolla/notification.rb +5 -0
  22. data/lib/active_merchant/billing/integrations/helper.rb +6 -1
  23. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +46 -4
  24. data/lib/active_merchant/billing/integrations/two_checkout.rb +1 -2
  25. data/lib/active_merchant/version.rb +1 -1
  26. metadata +66 -40
  27. metadata.gz.sig +0 -0
@@ -202,7 +202,7 @@ module ActiveMerchant #:nodoc:
202
202
 
203
203
  # Return money to a card that was previously billed.
204
204
  # _authorization_ should be the transaction id of the transaction we are returning.
205
- def credit(money, authorization, options = {})
205
+ def refund(money, authorization, options = {})
206
206
  parameters = {
207
207
  :transaction_amount => amount(money),
208
208
  :transaction_id => authorization
@@ -211,6 +211,11 @@ module ActiveMerchant #:nodoc:
211
211
  commit('CREDIT', parameters)
212
212
  end
213
213
 
214
+ def credit(money, authorization, options = {})
215
+ deprecated CREDIT_DEPRECATION_MESSAGE
216
+ refund(money, authorization, options)
217
+ end
218
+
214
219
  # Cancel a transaction that has been charged but has not yet made it
215
220
  # through the batch process.
216
221
  def void(authorization, options = {})
@@ -0,0 +1,120 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class SamuraiGateway < Gateway
4
+
5
+ self.homepage_url = 'https://samurai.feefighters.com'
6
+ self.display_name = 'Samurai'
7
+ self.supported_countries = ['US']
8
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
9
+ self.default_currency = 'USD'
10
+ self.money_format = :dollars
11
+
12
+ def initialize(options = {})
13
+ begin
14
+ require 'samurai'
15
+ rescue LoadError
16
+ raise "Could not load the samurai gem (>= 0.2.25). Use `gem install samurai` to install it."
17
+ end
18
+
19
+ requires!(options, :login, :password, :processor_token)
20
+ @options = options
21
+ Samurai.options = {
22
+ :merchant_key => options[:login],
23
+ :merchant_password => options[:password],
24
+ :processor_token => options[:processor_token]
25
+ }
26
+ end
27
+
28
+ def test?
29
+ @options[:test] || super
30
+ end
31
+
32
+ def authorize(money, credit_card_or_vault_id, options = {})
33
+ token = payment_method_token(credit_card_or_vault_id, options)
34
+ return token if token.is_a?(Response)
35
+
36
+ authorize = Samurai::Processor.authorize(token, amount(money), processor_options(options))
37
+ handle_result(authorize)
38
+ end
39
+
40
+ def purchase(money, credit_card_or_vault_id, options = {})
41
+ token = payment_method_token(credit_card_or_vault_id, options)
42
+ return token if token.is_a?(Response)
43
+
44
+ purchase = Samurai::Processor.purchase(token, amount(money), processor_options(options))
45
+ handle_result(purchase)
46
+ end
47
+
48
+ def capture(money, authorization_id, options = {})
49
+ transaction = Samurai::Transaction.find(authorization_id)
50
+ handle_result(transaction.capture(amount(money)))
51
+ end
52
+
53
+ def refund(money, transaction_id, options = {})
54
+ transaction = Samurai::Transaction.find(transaction_id)
55
+ handle_result(transaction.credit(amount(money)))
56
+ end
57
+
58
+ def void(transaction_id, options = {})
59
+ transaction = Samurai::Transaction.find(transaction_id)
60
+ handle_result(transaction.void)
61
+ end
62
+
63
+ def store(creditcard, options = {})
64
+ address = options[:billing_address] || options[:address] || {}
65
+
66
+ result = Samurai::PaymentMethod.create({
67
+ :card_number => creditcard.number,
68
+ :expiry_month => creditcard.month.to_s.rjust(2, "0"),
69
+ :expiry_year => creditcard.year.to_s,
70
+ :cvv => creditcard.verification_value,
71
+ :first_name => creditcard.first_name,
72
+ :last_name => creditcard.last_name,
73
+ :address_1 => address[:address1],
74
+ :address_2 => address[:address2],
75
+ :city => address[:city],
76
+ :zip => address[:zip],
77
+ :sandbox => test?
78
+ })
79
+
80
+ Response.new(result.is_sensitive_data_valid,
81
+ message_from_result(result),
82
+ { :payment_method_token => result.is_sensitive_data_valid && result.payment_method_token })
83
+ end
84
+
85
+ private
86
+
87
+ def payment_method_token(credit_card_or_vault_id, options)
88
+ return credit_card_or_vault_id if credit_card_or_vault_id.is_a?(String)
89
+ store_result = store(credit_card_or_vault_id, options)
90
+ store_result.success? ? store_result.params["payment_method_token"] : store_result
91
+ end
92
+
93
+ def handle_result(result)
94
+ response_params, response_options = {}, {}
95
+ if result.success?
96
+ response_options[:test] = test?
97
+ response_options[:authorization] = result.reference_id
98
+ response_params[:reference_id] = result.reference_id
99
+ response_params[:transaction_token] = result.transaction_token
100
+ response_params[:payment_method_token] = result.payment_method.payment_method_token
101
+ end
102
+
103
+ response_options[:avs_result] = { :code => result.processor_response && result.processor_response.avs_result_code }
104
+ response_options[:cvv_result] = result.processor_response && result.processor_response.cvv_result_code
105
+
106
+ message = message_from_result(result)
107
+ Response.new(result.success?, message, response_params, response_options)
108
+ end
109
+
110
+ def message_from_result(result)
111
+ return "OK" if result.success?
112
+ result.errors.map {|_, messages| messages }.join(" ")
113
+ end
114
+
115
+ def processor_options(options)
116
+ options.slice(:billing_reference, :customer_reference, :custom, :descriptor)
117
+ end
118
+ end
119
+ end
120
+ end
@@ -236,7 +236,7 @@ module ActiveMerchant #:nodoc:
236
236
  commit(:change_status, nil, post)
237
237
  end
238
238
 
239
- def credit(money, identification, options = {})
239
+ def refund(money, identification, options = {})
240
240
  post = {}
241
241
  add_status_action(post, 'CREDIT')
242
242
  add_forced_settlement(post, options)
@@ -244,6 +244,11 @@ module ActiveMerchant #:nodoc:
244
244
  commit(:change_status, money, post)
245
245
  end
246
246
 
247
+ def credit(money, identification, options = {})
248
+ deprecated CREDIT_DEPRECATION_MESSAGE
249
+ refund(money, identification, options)
250
+ end
251
+
247
252
  def status(order_id)
248
253
  commit(:get_status, nil, :szOrderNumber => order_id)
249
254
  end
@@ -1,194 +1,23 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
-
3
+ ##
4
+ # Delegates to the appropriate gateway, either the Transaction or Advanced
5
+ # depending on options passed to new.
6
+ #
4
7
  class UsaEpayGateway < Gateway
5
- URL = 'https://www.usaepay.com/gate.php'
6
-
7
- self.supported_cardtypes = [:visa, :master, :american_express]
8
- self.supported_countries = ['US']
9
- self.homepage_url = 'http://www.usaepay.com/'
10
- self.display_name = 'USA ePay'
11
8
 
12
- TRANSACTIONS = {
13
- :authorization => 'authonly',
14
- :purchase => 'sale',
15
- :capture => 'capture'
16
- }
17
-
18
- def initialize(options = {})
19
- requires!(options, :login)
20
- @options = options
21
- super
22
- end
23
-
24
- def authorize(money, credit_card, options = {})
25
- post = {}
26
-
27
- add_amount(post, money)
28
- add_invoice(post, options)
29
- add_credit_card(post, credit_card)
30
- add_address(post, credit_card, options)
31
- add_customer_data(post, options)
32
-
33
- commit(:authorization, post)
34
- end
35
-
36
- def purchase(money, credit_card, options = {})
37
- post = {}
38
-
39
- add_amount(post, money)
40
- add_invoice(post, options)
41
- add_credit_card(post, credit_card)
42
- add_address(post, credit_card, options)
43
- add_customer_data(post, options)
44
-
45
- commit(:purchase, post)
46
- end
47
-
48
- def capture(money, authorization, options = {})
49
- post = {
50
- :refNum => authorization
51
- }
52
-
53
- add_amount(post, money)
54
- commit(:capture, post)
55
- end
56
-
57
- private
58
-
59
- def add_amount(post, money)
60
- post[:amount] = amount(money)
61
- end
62
-
63
- def expdate(credit_card)
64
- year = format(credit_card.year, :two_digits)
65
- month = format(credit_card.month, :two_digits)
66
-
67
- "#{month}#{year}"
68
- end
69
-
70
- def add_customer_data(post, options)
71
- address = options[:billing_address] || options[:address] || {}
72
- post[:street] = address[:address1]
73
- post[:zip] = address[:zip]
74
-
75
- if options.has_key? :email
76
- post[:custemail] = options[:email]
77
- post[:custreceipt] = 'No'
78
- end
79
-
80
- if options.has_key? :customer
81
- post[:custid] = options[:customer]
82
- end
83
-
84
- if options.has_key? :ip
85
- post[:ip] = options[:ip]
86
- end
87
- end
88
-
89
- def add_address(post, credit_card, options)
90
- billing_address = options[:billing_address] || options[:address]
91
-
92
- add_address_for_type(:billing, post, credit_card, billing_address) if billing_address
93
- add_address_for_type(:shipping, post, credit_card, options[:shipping_address]) if options[:shipping_address]
94
- end
95
-
96
- def add_address_for_type(type, post, credit_card, address)
97
- prefix = address_key_prefix(type)
98
-
99
- post[address_key(prefix, 'fname')] = credit_card.first_name
100
- post[address_key(prefix, 'lname')] = credit_card.last_name
101
- post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank?
102
- post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank?
103
- post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank?
104
- post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank?
105
- post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank?
106
- post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank?
107
- post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank?
108
- post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank?
109
- end
110
-
111
- def address_key_prefix(type)
112
- case type
113
- when :shipping then 'ship'
114
- when :billing then 'bill'
115
- end
116
- end
117
-
118
- def address_key(prefix, key)
119
- "#{prefix}#{key}".to_sym
120
- end
121
-
122
- def add_invoice(post, options)
123
- post[:invoice] = options[:order_id]
124
- end
125
-
126
- def add_credit_card(post, credit_card)
127
- post[:card] = credit_card.number
128
- post[:cvv2] = credit_card.verification_value if credit_card.verification_value?
129
- post[:expir] = expdate(credit_card)
130
- post[:name] = credit_card.name
131
- end
132
-
133
- def parse(body)
134
- fields = {}
135
- for line in body.split('&')
136
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
137
- fields[key] = CGI.unescape(value.to_s)
138
- end
139
-
140
- {
141
- :status => fields['UMstatus'],
142
- :auth_code => fields['UMauthCode'],
143
- :ref_num => fields['UMrefNum'],
144
- :batch => fields['UMbatch'],
145
- :avs_result => fields['UMavsResult'],
146
- :avs_result_code => fields['UMavsResultCode'],
147
- :cvv2_result => fields['UMcvv2Result'],
148
- :cvv2_result_code => fields['UMcvv2ResultCode'],
149
- :vpas_result_code => fields['UMvpasResultCode'],
150
- :result => fields['UMresult'],
151
- :error => fields['UMerror'],
152
- :error_code => fields['UMerrorcode'],
153
- :acs_url => fields['UMacsurl'],
154
- :payload => fields['UMpayload']
155
- }.delete_if{|k, v| v.nil?}
156
- end
157
-
158
-
159
- def commit(action, parameters)
160
- response = parse( ssl_post(URL, post_data(action, parameters)) )
161
-
162
- Response.new(response[:status] == 'Approved', message_from(response), response,
163
- :test => @options[:test] || test?,
164
- :authorization => response[:ref_num],
165
- :cvv_result => response[:cvv2_result_code],
166
- :avs_result => {
167
- :street_match => response[:avs_result_code].to_s[0,1],
168
- :postal_match => response[:avs_result_code].to_s[1,1],
169
- :code => response[:avs_result_code].to_s[2,1]
170
- }
171
- )
172
- end
173
-
174
- def message_from(response)
175
- if response[:status] == "Approved"
176
- return 'Success'
9
+ ##
10
+ # Creates an instance of UsaEpayTransactionGateway by default, but if
11
+ # :software id or :live_url are passed in the options hash it will
12
+ # create an instance of UsaEpayAdvancedGateway.
13
+ #
14
+ def self.new(options={})
15
+ unless options.has_key?(:software_id) || options.has_key?(:live_url)
16
+ UsaEpayTransactionGateway.new(options)
177
17
  else
178
- return 'Unspecified error' if response[:error].blank?
179
- return response[:error]
18
+ UsaEpayAdvancedGateway.new(options)
180
19
  end
181
20
  end
182
-
183
- def post_data(action, parameters = {})
184
- parameters[:command] = TRANSACTIONS[action]
185
- parameters[:key] = @options[:login]
186
- parameters[:software] = 'Active Merchant'
187
- parameters[:testmode] = @options[:test] ? 1 : 0
188
-
189
- parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join("&")
190
- end
191
21
  end
192
22
  end
193
23
  end
194
-
@@ -0,0 +1,1496 @@
1
+ require 'securerandom'
2
+ require 'digest'
3
+
4
+ module ActiveMerchant #:nodoc:
5
+ module Billing #:nodoc:
6
+ # ==== USA ePay Advanced SOAP Interface
7
+ #
8
+ # This class encapuslates USA ePay's Advanced SOAP Interface. The Advanced Soap Interface allows
9
+ # standard transactions, storing customer information, and recurring billing. Storing sensitive
10
+ # information on USA ePay's servers can help with PCI DSS compliance, since customer and card data
11
+ # do not need to be stored locally.
12
+ #
13
+ # Make sure you have enabled this functionality for your account with USA ePay.
14
+ #
15
+ # Information about the Advanced SOAP interface is available on the {USA ePay wiki}[http://wiki.usaepay.com/developer/soap].
16
+ #
17
+ # ==== Login, Password, and Software ID
18
+ #
19
+ # Please follow all of USA ePay's directions for acquiring all accounts and settings.
20
+ #
21
+ # The value used for <tt>:login</tt> is the Key value found in the Merchant Console under Settings > Source
22
+ # Key. You will have to add this key in the USA ePay Merchant Console.
23
+ #
24
+ # The value used for <tt>:password</tt> is the pin value also found and assigned in the Merchant Console under
25
+ # Settings > Source Key. The pin is required to use all but basic transactions in the SOAP interface.
26
+ # You will have to add the pin to your source key, as it defaults to none.
27
+ #
28
+ # The value used for the <tt>:software_id</tt> is found in the Developer's Login under the Developers Center
29
+ # in your WSDL. It is the 8 character value in <soap:address> tag. A masked example:
30
+ # <soap:address location="https://www.usaepay.com/soap/gate/XXXXXXXX"/>
31
+ # It is also found in the link to your WSDL. This is required as every account has a different path
32
+ # SOAP requests are submitted to. Optionally, you can provide the entire urls via <tt>:live_url</tt> and <tt>:test_url</tt>, if your prefer.
33
+ #
34
+ # ==== Responses
35
+ # * <tt>#success?</tt> -- +true+ if transmitted and returned correctly
36
+ # * <tt>#message</tt> -- response or fault message
37
+ # * <tt>#authorization</tt> -- reference_number or nil
38
+ # * <tt>#params</tt> -- hash of entire soap response contents
39
+ #
40
+ # ==== Address Options
41
+ # * <tt>:billing_address/:shipping_address</tt> -- contains some extra options
42
+ # * <tt>:name</tt> -- virtual attribute; will split to first and last name
43
+ # * <tt>:first_name</tt>
44
+ # * <tt>:last_name</tt>
45
+ # * <tt>:address1 </tt>
46
+ # * <tt>:address2 </tt>
47
+ # * <tt>:city </tt>
48
+ # * <tt>:state </tt>
49
+ # * <tt>:zip </tt>
50
+ # * <tt>:country </tt>
51
+ # * <tt>:phone</tt>
52
+ # * <tt>:email</tt>
53
+ # * <tt>:fax</tt>
54
+ # * <tt>:company</tt>
55
+ #
56
+ # ==== Support:
57
+ # * Questions: post to {active_merchant google group}[http://groups.google.com/group/activemerchant]
58
+ # * Feedback/fixes: matt (at) nearapogee (dot) com
59
+ #
60
+ # ==== Links:
61
+ # * {USA ePay Merchant Console}[https://sandbox.usaepay.com/login]
62
+ # * {USA ePay Developer Login}[https://www.usaepay.com/developer/login]
63
+ #
64
+ class UsaEpayAdvancedGateway < Gateway
65
+ API_VERSION = "1.4"
66
+
67
+ class_attribute :test_url, :live_url
68
+
69
+ TEST_URL_BASE = 'https://sandbox.usaepay.com/soap/gate/' #:nodoc:
70
+ LIVE_URL_BASE = 'https://www.usaepay.com/soap/gate/' #:nodoc:
71
+
72
+ FAILURE_MESSAGE = "Default Failure" #:nodoc:
73
+
74
+ self.supported_countries = ['US']
75
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
76
+ self.homepage_url = 'http://www.usaepay.com/'
77
+ self.display_name = 'USA ePay Advanced SOAP Interface'
78
+
79
+ CUSTOMER_OPTIONS = {
80
+ :id => [:string, 'CustomerID'], # merchant assigned number
81
+ :notes => [:string, 'Notes'],
82
+ :data => [:string, 'CustomData'],
83
+ :url => [:string, 'URL'],
84
+ # Recurring Billing
85
+ :enabled => [:boolean, 'Enabled'],
86
+ :schedule => [:string, 'Schedule'],
87
+ :number_left => [:integer, 'NumLeft'],
88
+ :currency => [:string, 'Currency'],
89
+ :description => [:string, 'Description'],
90
+ :order_id => [:string, 'OrderID'],
91
+ :user => [:string, 'User'],
92
+ :source => [:string, 'Source'],
93
+ :send_receipt => [:boolean, 'SendReceipt'],
94
+ :receipt_note => [:string, 'ReceiptNote'],
95
+ # Point of Sale
96
+ :price_tier => [:string, 'PriceTier'],
97
+ :tax_class => [:string, 'TaxClass'],
98
+ :lookup_code => [:string, 'LookupCode']
99
+ } #:nodoc:
100
+
101
+ ADDRESS_OPTIONS = {
102
+ :first_name => [:string, 'FirstName'],
103
+ :last_name => [:string, 'LastName'],
104
+ :address1 => [:string, 'Street'],
105
+ :address2 => [:string, 'Street2'],
106
+ :city => [:string, 'City'],
107
+ :state => [:string, 'State'],
108
+ :zip => [:string, 'Zip'],
109
+ :country => [:string, 'Country'],
110
+ :phone => [:string, 'Phone'],
111
+ :email => [:string, 'Email'],
112
+ :fax => [:string, 'Fax'],
113
+ :company => [:string, 'Company']
114
+ } #:nodoc:
115
+
116
+ CUSTOMER_TRANSACTION_REQUEST_OPTIONS = {
117
+ :command => [:string, 'Command'],
118
+ :ignore_duplicate => [:boolean, 'IgnoreDuplicate'],
119
+ :client_ip => [:string, 'ClientIP'],
120
+ :customer_receipt => [:boolean, 'CustReceipt'],
121
+ :customer_email => [:boolean, 'CustReceiptEmail'],
122
+ :customer_template => [:boolean, 'CustReceiptName'],
123
+ :merchant_receipt => [:boolean, 'MerchReceipt'],
124
+ :merchant_email => [:boolean, 'MerchReceiptEmail'],
125
+ :merchant_template => [:boolean, 'MerchReceiptName'],
126
+ :verification_value => [:boolean, 'isRecurring'],
127
+ :software => [:string, 'Software']
128
+ } #:nodoc:
129
+
130
+ TRANSACTION_REQUEST_OBJECT_OPTIONS = {
131
+ :command => [:string, 'Command'],
132
+ :ignore_duplicate => [:boolean, 'IgnoreDuplicate'],
133
+ :authorization_code => [:string, 'AuthCode'],
134
+ :reference_number => [:string, 'RefNum'],
135
+ :account_holder => [:string, 'AccountHolder'],
136
+ :client_ip => [:string, 'ClientIP'],
137
+ :customer_id => [:string, 'CustomerID'],
138
+ :customer_receipt => [:boolean, 'CustReceipt'],
139
+ :customer_template => [:boolean, 'CustReceiptName'],
140
+ :software => [:string, 'Software']
141
+ } #:nodoc:
142
+
143
+ TRANSACTION_DETAIL_OPTIONS = {
144
+ :invoice => [:string, 'Invoice'],
145
+ :po_number => [:string, 'PONum'],
146
+ :order_id => [:string, 'OrderID'],
147
+ :clerk => [:string, 'Clerk'],
148
+ :terminal => [:string, 'Terminal'],
149
+ :table => [:string, 'Table'],
150
+ :description => [:string, 'Description'],
151
+ :comments => [:string, 'Comments'],
152
+ :allow_partial_auth => [:boolean, 'AllowPartialAuth'],
153
+ :currency => [:string, 'Currency'],
154
+ :non_tax => [:boolean, 'NonTax'],
155
+ } #:nodoc:
156
+
157
+ TRANSACTION_DETAIL_MONEY_OPTIONS = {
158
+ :amount => [:double, 'Amount'],
159
+ :tax => [:double, 'Tax'],
160
+ :tip => [:double, 'Tip'],
161
+ :non_tax => [:boolean, 'NonTax'],
162
+ :shipping => [:double, 'Shipping'],
163
+ :discount => [:double, 'Discount'],
164
+ :subtotal => [:double, 'Subtotal']
165
+ } #:nodoc:
166
+
167
+ CREDIT_CARD_DATA_OPTIONS = {
168
+ :magnetic_stripe => [:string, 'MagStripe'],
169
+ :dukpt => [:string, 'DUKPT'],
170
+ :signature => [:string, 'Signature'],
171
+ :terminal_type => [:string, 'TermType'],
172
+ :magnetic_support => [:string, 'MagSupport'],
173
+ :xid => [:string, 'XID'],
174
+ :cavv => [:string, 'CAVV'],
175
+ :eci => [:integer, 'ECI'],
176
+ :internal_card_authorization => [:boolean, 'InternalCardAduth'],
177
+ :pares => [:string, 'Pares']
178
+ } #:nodoc:
179
+
180
+ CHECK_DATA_OPTIONS = {
181
+ :check_number => [:integer, 'CheckNumber'],
182
+ :drivers_license => [:string, 'DriversLicense'],
183
+ :drivers_license_state => [:string, 'DriversLicenseState'],
184
+ :record_type => [:string, 'RecordType'],
185
+ :aux_on_us => [:string, 'AuxOnUS'],
186
+ :epc_code => [:string, 'EpcCode'],
187
+ :front_image => [:string, 'FrontImage'],
188
+ :back_image => [:string, 'BackImage']
189
+ } #:nodoc:
190
+
191
+ RECURRING_BILLING_OPTIONS = {
192
+ :schedule => [:string, 'Schedule'],
193
+ :number_left => [:integer, 'NumLeft'],
194
+ :enabled => [:boolean, 'Enabled']
195
+ } #:nodoc:
196
+
197
+ AVS_RESULTS = {
198
+ 'Y' => %w( YYY Y YYA YYD ),
199
+ 'Z' => %w( NYZ Z ),
200
+ 'A' => %w( YNA A YNY ),
201
+ 'N' => %w( NNN N NN ),
202
+ 'X' => %w( YYX X ),
203
+ 'W' => %w( NYW W ),
204
+ 'XXW' => %w( XXW ),
205
+ 'XXU' => %w( XXU ),
206
+ 'R' => %w( XXR R U E ),
207
+ 'S' => %w( XXS S ),
208
+ 'XXE' => %w( XXE ),
209
+ 'G' => %w( XXG G C I ),
210
+ 'B' => %w( YYG B M ),
211
+ 'D' => %w( GGG D ),
212
+ 'P' => %w( YGG P )
213
+ }.inject({}) do |map, (type, codes)|
214
+ codes.each { |code| map[code] = type }
215
+ map
216
+ end #:nodoc:
217
+
218
+ AVS_CUSTOM_MESSAGES = {
219
+ 'XXW' => 'Card number not on file.',
220
+ 'XXU' => 'Address information not verified for domestic transaction.',
221
+ 'XXE' => 'Address verification not allowed for card type.'
222
+ } #:nodoc:
223
+
224
+ # Create a new gateway.
225
+ #
226
+ # ==== Required
227
+ # * At least the live_url OR the software_id must be present.
228
+ # * <tt>:software_id</tt> -- 8 character software id
229
+ # OR
230
+ # * <tt>:test_url</tt> -- full url for testing
231
+ # * <tt>:live_url</tt> -- full url for live/production
232
+ #
233
+ # ==== Optional
234
+ # * <tt>:soap_response</tt> -- set to +true+ to add :soap_response to the params hash containing the entire soap xml message
235
+ #
236
+ def initialize(options = {})
237
+ requires! options, :login, :password
238
+ @options = options
239
+
240
+ if @options[:software_id]
241
+ self.live_url = "#{LIVE_URL_BASE}#{@options[:software_id].to_s}"
242
+ self.test_url = "#{TEST_URL_BASE}#{@options[:software_id].to_s}"
243
+ else
244
+ self.live_url = @options[:live_url].to_s
245
+ self.test_url = @options[:test_url].to_s if @options[:test_url]
246
+ end
247
+
248
+ super
249
+ end
250
+
251
+ # Standard Gateway Methods ======================================
252
+
253
+ # Make a purchase with a credit card. (Authorize and
254
+ # capture for settlement.)
255
+ #
256
+ # Note: See run_transaction for additional options.
257
+ #
258
+ def purchase(money, creditcard, options={})
259
+ run_sale(options.merge!(:amount => money, :payment_method => creditcard))
260
+ end
261
+
262
+ # Authorize an amount on a credit card or account.
263
+ #
264
+ # Note: See run_transaction for additional options.
265
+ #
266
+ def authorize(money, creditcard, options={})
267
+ run_auth_only(options.merge!(:amount => money, :payment_method => creditcard))
268
+ end
269
+
270
+ # Capture an authorized transaction.
271
+ #
272
+ # Note: See run_transaction for additional options.
273
+ #
274
+ def capture(money, identification, options={})
275
+ capture_transaction(options.merge!(:amount => money, :reference_number => identification))
276
+ end
277
+
278
+ # Void a previous transaction that has not been settled.
279
+ #
280
+ # Note: See run_transaction for additional options.
281
+ #
282
+ def void(identification, options={})
283
+ void_transaction(options.merge!(:reference_number => identification))
284
+ end
285
+
286
+ # Credit a previous transaction.
287
+ #
288
+ # Note: See run_transaction for additional options.
289
+ #
290
+ def credit(money, identification, options={})
291
+ refund_transaction(options.merge!(:amount => money, :reference_number => identification))
292
+ end
293
+
294
+ # Customer ======================================================
295
+
296
+ # Add a customer.
297
+ #
298
+ # ==== Options
299
+ # * <tt>:id</tt> -- merchant assigned id
300
+ # * <tt>:notes</tt> -- notes about customer
301
+ # * <tt>:data</tt> -- base64 data about customer
302
+ # * <tt>:url</tt> -- customer website
303
+ # * <tt>:billing_address</tt> -- usual options
304
+ # * <tt>:payment_methods</tt> -- array of payment method hashes.
305
+ # * <tt>:method</tt> -- credit_card or check
306
+ # * <tt>:name</tt> -- optional name/label for the method
307
+ # * <tt>:sort</tt> -- optional integer value specifying the backup sort order, 0 is default
308
+ #
309
+ # ==== Recurring Options
310
+ # * <tt>:enabled</tt> -- +true+ enables recurring
311
+ # * <tt>:schedule</tt> -- daily, weekly, bi-weekly (every two weeks), monthly, bi-monthly (every two months), quarterly, bi-annually (every six months), annually, first of month, last day of month
312
+ # * <tt>:number_left</tt> -- number of payments left; -1 for unlimited
313
+ # * <tt>:next</tt> -- date of next payment (Date/Time)
314
+ # * <tt>:amount</tt> -- amount of recurring payment
315
+ # * <tt>:tax</tt> -- tax portion of amount
316
+ # * <tt>:currency</tt> -- numeric currency code
317
+ # * <tt>:description</tt> -- description of transaction
318
+ # * <tt>:order_id</tt> -- transaction order id
319
+ # * <tt>:user</tt> -- merchant username assigned to transaction
320
+ # * <tt>:source</tt> -- name of source key assigned to billing
321
+ # * <tt>:send_receipt</tt> -- +true+ to send client a receipt
322
+ # * <tt>:receipt_note</tt> -- leave a note on the receipt
323
+ #
324
+ # ==== Point of Sale Options
325
+ # * <tt>:price_tier</tt> -- name of customer price tier
326
+ # * <tt>:tax_class</tt> -- tax class
327
+ # * <tt>:lookup_code</tt> -- lookup code from customer/member id card; barcode or magnetic stripe; can be assigned by merchant; defaults to system assigned if blank
328
+ #
329
+ # ==== Response
330
+ # * <tt>#message</tt> -- customer number assigned by gateway
331
+ #
332
+ def add_customer(options={})
333
+ request = build_request(__method__, options)
334
+ commit(__method__, request)
335
+ end
336
+
337
+ # Update a customer by replacing all of the customer details..
338
+ #
339
+ # Use quickUpdateCustomer to just update a few attributes.
340
+ #
341
+ # ==== Options
342
+ # * Same as add_customer
343
+ #
344
+ def update_customer(options={})
345
+ requires! options, :customer_number
346
+
347
+ request = build_request(__method__, options)
348
+ commit(__method__, request)
349
+ end
350
+
351
+ # Enable a customer for recurring billing.
352
+ #
353
+ # Note: Customer does not need to have all recurring paramerters to succeed.
354
+ #
355
+ # ==== Required
356
+ # * <tt>:customer_number</tt>
357
+ #
358
+ def enable_customer(options={})
359
+ requires! options, :customer_number
360
+
361
+ request = build_request(__method__, options)
362
+ commit(__method__, request)
363
+ end
364
+
365
+ # Disable a customer for recurring billing.
366
+ #
367
+ # ==== Required
368
+ # * <tt>:customer_number</tt>
369
+ #
370
+ def disable_customer(options={})
371
+ requires! options, :customer_number
372
+
373
+ request = build_request(__method__, options)
374
+ commit(__method__, request)
375
+ end
376
+
377
+ # Add a payment method to a customer.
378
+ #
379
+ # ==== Required
380
+ # * <tt>:customer_number</tt> -- number returned by add_customer response.message
381
+ # * <tt>:payment_method</tt>
382
+ # * <tt>:method</tt> -- credit_card or check
383
+ # * <tt>:name</tt> -- optional name/label for the method
384
+ # * <tt>:sort</tt> -- an integer value specifying the backup sort order, 0 is default
385
+ #
386
+ # ==== Optional
387
+ # * <tt>:make_default</tt> -- set +true+ to make default
388
+ # * <tt>:verify</tt> -- set +true+ to run auth_only verification; throws fault if cannot verify
389
+ #
390
+ # ==== Response
391
+ # * <tt>#message</tt> -- method_id of new customer payment method
392
+ #
393
+ def add_customer_payment_method(options={})
394
+ requires! options, :customer_number
395
+
396
+ request = build_request(__method__, options)
397
+ commit(__method__, request)
398
+ end
399
+
400
+ # Retrive all of the payment methods belonging to a customer
401
+ #
402
+ # ==== Required
403
+ # * <tt>:customer_number</tt>
404
+ #
405
+ # ==== Response
406
+ # * <tt>#message</tt> -- either a single hash or an array of hashes of payment methods
407
+ #
408
+ def get_customer_payment_methods(options={})
409
+ requires! options, :customer_number
410
+
411
+ request = build_request(__method__, options)
412
+ commit(__method__, request)
413
+ end
414
+
415
+ # Retrive one of the payment methods belonging to a customer
416
+ #
417
+ # ==== Required
418
+ # * <tt>:customer_number</tt>
419
+ # * <tt>:method_id</tt>
420
+ #
421
+ # ==== Response
422
+ # * <tt>#message</tt> -- hash of payment method
423
+ #
424
+ def get_customer_payment_method(options={})
425
+ requires! options, :customer_number, :method_id
426
+
427
+ request = build_request(__method__, options)
428
+ commit(__method__, request)
429
+ end
430
+
431
+ # Update a customer payment method.
432
+ #
433
+ # ==== Required
434
+ # * <tt>:method_id</tt> -- method_id to update
435
+ #
436
+ # ==== Options
437
+ # * <tt>:method</tt> -- credit_card or check
438
+ # * <tt>:name</tt> -- optional name/label for the method
439
+ # * <tt>:sort</tt> -- an integer value specifying the backup sort order, 0 is default
440
+ # * <tt>:verify</tt> -- set +true+ to run auth_only verification; throws fault if cannot verify
441
+ #
442
+ # ==== Response
443
+ # * <tt>#message</tt> -- hash of payment method
444
+ #
445
+ def update_customer_payment_method(options={})
446
+ requires! options, :method_id
447
+
448
+ request = build_request(__method__, options)
449
+ commit(__method__, request)
450
+ end
451
+
452
+ # Delete one the payment methods beloning to a customer
453
+ #
454
+ # ==== Required
455
+ # * <tt>:customer_number</tt>
456
+ # * <tt>:method_id</tt>
457
+ #
458
+ def delete_customer_payment_method(options={})
459
+ requires! options, :customer_number, :method_id
460
+
461
+ request = build_request(__method__, options)
462
+ commit(__method__, request)
463
+ end
464
+
465
+ # Delete a customer.
466
+ #
467
+ # ==== Required
468
+ # * <tt>:customer_number</tt>
469
+ #
470
+ def delete_customer(options={})
471
+ requires! options, :customer_number
472
+
473
+ request = build_request(__method__, options)
474
+ commit(__method__, request)
475
+ end
476
+
477
+ # Run a transaction for an existing customer in the database.
478
+ #
479
+ # ==== Required Options
480
+ # * <tt>:customer_number</tt> -- gateway assigned identifier
481
+ # * <tt>:command</tt> -- Sale, AuthOnly, Credit, Check, CheckCredit
482
+ # * <tt>:amount</tt> -- total amount
483
+ #
484
+ # ==== Options
485
+ # * <tt>:method_id</tt> -- which payment method to use, 0/nil/omitted for default method
486
+ # * <tt>:ignore_duplicate</tt> -- +true+ overrides duplicate transaction
487
+ # * <tt>:client_ip</tt> -- client ip address
488
+ # * <tt>:customer_receipt</tt> -- +true+, sends receipt to customer. active_merchant defaults to +false+
489
+ # * <tt>:customer_email</tt> -- specify if different than customer record
490
+ # * <tt>:customer_template</tt> -- name of template
491
+ # * <tt>:merchant_receipt</tt> -- +true+, sends receipt to merchant. active_merchant defaults to +false+
492
+ # * <tt>:merchant_email</tt> -- required if :merchant_receipt set to +true+
493
+ # * <tt>:merchant_template</tt> -- name of template
494
+ # * <tt>:recurring</tt> -- defaults to +false+ *see documentation*
495
+ # * <tt>:verification_value</tt> -- pci forbids storage of this value, only required for CVV2 validation
496
+ # * <tt>:software</tt> -- active_merchant sets to required gateway option value
497
+ # * <tt>:line_items</tt> -- XXX not implemented yet
498
+ # * <tt>:custom_fields</tt> -- XXX not implemented yet
499
+ #
500
+ # ==== Transaction Options
501
+ # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number
502
+ # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters
503
+ # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters
504
+ # * <tt>:clerk</tt> -- sales clerk
505
+ # * <tt>:terminal</tt> -- terminal name
506
+ # * <tt>:table</tt> -- table name/number
507
+ # * <tt>:description</tt> -- description
508
+ # * <tt>:comments</tt> -- comments
509
+ # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+
510
+ # * <tt>:currency</tt> -- numeric currency code
511
+ # * <tt>:tax</tt> -- tax portion of amount
512
+ # * <tt>:tip</tt> -- tip portion of amount
513
+ # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable
514
+ # * <tt>:shipping</tt> -- shipping portion of amount
515
+ # * <tt>:discount</tt> -- amount of discount
516
+ # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied
517
+ #
518
+ # ==== Response
519
+ # * <tt>#message</tt> -- transaction response hash
520
+ #
521
+ def run_customer_transaction(options={})
522
+ requires! options, :customer_number, :command, :amount
523
+
524
+ request = build_request(__method__, options)
525
+ commit(__method__, request)
526
+ end
527
+
528
+ # Transactions ==================================================
529
+
530
+ # Run a transaction.
531
+ #
532
+ # Note: run_sale, run_auth_only, run_credit, run_check_sale, run_check_credit
533
+ # methods are also available. Each takes the same options as
534
+ # run_transaction, but the :command option is not required.
535
+ #
536
+ # Recurring Note: If recurring options are included USA ePay will create a
537
+ # new customer record with the supplied information. The customer number
538
+ # will be returned in the response.
539
+ #
540
+ # ==== Options
541
+ # * <tt>:method</tt> -- credit_card or check
542
+ # * <tt>:command</tt> -- sale, credit, void, creditvoid, authonly, capture, postauth, check, checkcredit; defaults to sale; only required for run_transaction when other than sale
543
+ # * <tt>:reference_number</tt> -- for the original transaction; obtained by sale or authonly
544
+ # * <tt>:authorization_code</tt> -- required for postauth; obtained offline
545
+ # * <tt>:ignore_duplicate</tt> -- set +true+ if you want to override the duplicate tranaction handling
546
+ # * <tt>:account_holder</tt> -- name of account holder
547
+ # * <tt>:customer_id</tt> -- merchant assigned id
548
+ # * <tt>:customer_receipt</tt> -- set +true+ to email receipt to billing email address
549
+ # * <tt>:customer_template</tt> -- name of template
550
+ # * <tt>:software</tt> -- stamp merchant software version for tracking
551
+ # * <tt>:billing_address</tt> -- see UsaEpayCimGateway documentation for all address fields
552
+ # * <tt>:shipping_address</tt> -- see UsaEpayCimGateway documentation for all address fields
553
+ # * <tt>:recurring</tt> -- used for recurring billing transactions
554
+ # * <tt>:schedule</tt> -- disabled, daily, weekly, bi-weekly (every two weeks), monthly, bi-monthly (every two months), quarterly, bi-annually (every six months), annually
555
+ # * <tt>:next</tt> -- date customer billed next (Date/Time)
556
+ # * <tt>:expire</tt> -- date the recurring transactions end (Date/Time)
557
+ # * <tt>:number_left</tt> -- transactions remaining in billing cycle
558
+ # * <tt>:amount</tt> -- amount to be billed each recurring transaction
559
+ # * <tt>:enabled</tt> -- states if currently active
560
+ # * <tt>:line_items</tt> -- XXX not implemented yet
561
+ # * <tt>:custom_fields</tt> -- XXX not implemented yet
562
+ #
563
+ # ==== Transaction Options
564
+ # * <tt>:amount</tt> -- total amount
565
+ # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number
566
+ # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters
567
+ # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters
568
+ # * <tt>:clerk</tt> -- sales clerk
569
+ # * <tt>:terminal</tt> -- terminal name
570
+ # * <tt>:table</tt> -- table name/number
571
+ # * <tt>:description</tt> -- description
572
+ # * <tt>:comments</tt> -- comments
573
+ # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+
574
+ # * <tt>:currency</tt> -- numeric currency code
575
+ # * <tt>:tax</tt> -- tax portion of amount
576
+ # * <tt>:tip</tt> -- tip portion of amount
577
+ # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable
578
+ # * <tt>:shipping</tt> -- shipping portion of amount
579
+ # * <tt>:discount</tt> -- amount of discount
580
+ # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied
581
+ #
582
+ # ==== Response
583
+ # * <tt>#message</tt> -- transaction response hash
584
+ #
585
+ def run_transaction(options={})
586
+ request = build_request(__method__, options)
587
+ commit(__method__, request)
588
+ end
589
+
590
+ TRANSACTION_METHODS = [
591
+ :run_sale, :run_auth_only, :run_credit,
592
+ :run_check_sale, :run_check_credit
593
+ ] #:nodoc:
594
+
595
+ TRANSACTION_METHODS.each do |method|
596
+ define_method method do |options|
597
+ request = build_request(method, options)
598
+ commit(method, request)
599
+ end
600
+ end
601
+
602
+ # Post an authorization code obtained offline.
603
+ #
604
+ # ==== Required
605
+ # * <tt>:authorization_code</tt> -- obtained offline
606
+ #
607
+ # ==== Options
608
+ # * Same as run_transaction
609
+ #
610
+ # ==== Response
611
+ # * <tt>#message</tt> -- transaction response hash
612
+ #
613
+ def post_auth(options={})
614
+ requires! options, :authorization_code
615
+
616
+ request = build_request(__method__, options)
617
+ commit(__method__, request)
618
+ end
619
+
620
+ # Capture an authorized transaction and move it into the current batch
621
+ # for settlement.
622
+ #
623
+ # Note: Check with merchant bank for details/restrictions on differing
624
+ # amounts than the original authorization.
625
+ #
626
+ # ==== Required
627
+ # * <tt>:reference_number</tt>
628
+ #
629
+ # ==== Options
630
+ # * <tt>:amount</tt> -- may be different than original amount; 0 will void authorization
631
+ #
632
+ # ==== Response
633
+ # * <tt>#message</tt> -- transaction response hash
634
+ #
635
+ def capture_transaction(options={})
636
+ requires! options, :reference_number
637
+
638
+ request = build_request(__method__, options)
639
+ commit(__method__, request)
640
+ end
641
+
642
+ # Void a transaction.
643
+ #
644
+ # Note: Can only be voided before being settled.
645
+ #
646
+ # ==== Required
647
+ # * <tt>:reference_number</tt>
648
+ #
649
+ # ==== Response
650
+ # * <tt>#message</tt> -- transaction response hash
651
+ #
652
+ def void_transaction(options={})
653
+ requires! options, :reference_number
654
+
655
+ request = build_request(__method__, options)
656
+ commit(__method__, request)
657
+ end
658
+
659
+ # Refund transaction.
660
+ #
661
+ # Note: Required after a transaction has been settled. Refunds
662
+ # both credit card and check transactions.
663
+ #
664
+ # ==== Required
665
+ # * <tt>:reference_number</tt>
666
+ # * <tt>:amount</tt> -- amount to refund; 0 will refund original amount
667
+ #
668
+ # ==== Response
669
+ # * <tt>#message</tt> -- transaction response hash
670
+ #
671
+ def refund_transaction(options={})
672
+ requires! options, :reference_number, :amount
673
+
674
+ request = build_request(__method__, options)
675
+ commit(__method__, request)
676
+ end
677
+
678
+ # Override transaction flagged for mananager approval.
679
+ #
680
+ # Note: Checks only!
681
+ #
682
+ # ==== Required
683
+ # * <tt>:reference_number</tt>
684
+ #
685
+ # ==== Options
686
+ # * <tt>:reason</tt>
687
+ #
688
+ # ==== Response
689
+ # * <tt>#message</tt> -- transaction response hash
690
+ #
691
+ def override_transaction(options={})
692
+ requires! options, :reference_number
693
+
694
+ request = build_request(__method__, options)
695
+ commit(__method__, request)
696
+ end
697
+
698
+ # Quick Transactions ============================================
699
+
700
+ # Run a sale transaction based off of a past transaction.
701
+ #
702
+ # Transfers referenced transaction's payment method to this
703
+ # transaction. As of 6/2011, USA ePay blocks credit card numbers
704
+ # at 3 years.
705
+ #
706
+ # ==== Required
707
+ # * <tt>:reference_number</tt> -- transaction to reference payment from
708
+ # * <tt>:amount</tt> -- total amount
709
+ #
710
+ # ==== Options
711
+ # * <tt>:authorize_only</tt> -- set +true+ if you just want to authorize
712
+ #
713
+ # ==== Transaction Options
714
+ # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number
715
+ # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters
716
+ # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters
717
+ # * <tt>:clerk</tt> -- sales clerk
718
+ # * <tt>:terminal</tt> -- terminal name
719
+ # * <tt>:table</tt> -- table name/number
720
+ # * <tt>:description</tt> -- description
721
+ # * <tt>:comments</tt> -- comments
722
+ # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+
723
+ # * <tt>:currency</tt> -- numeric currency code
724
+ # * <tt>:tax</tt> -- tax portion of amount
725
+ # * <tt>:tip</tt> -- tip portion of amount
726
+ # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable
727
+ # * <tt>:shipping</tt> -- shipping portion of amount
728
+ # * <tt>:discount</tt> -- amount of discount
729
+ # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied
730
+ #
731
+ # ==== Response
732
+ # * <tt>#message</tt> -- transaction response hash
733
+ #
734
+ def run_quick_sale(options={})
735
+ requires! options, :reference_number, :amount
736
+
737
+ request = build_request(__method__, options)
738
+ commit(__method__, request)
739
+ end
740
+
741
+ # Run a credit based off of a past transaction.
742
+ #
743
+ # Transfers referenced transaction's payment method to this
744
+ # transaction. As of 6/2011, USA ePay blocks credit card numbers
745
+ # at 3 years.
746
+ #
747
+ # ==== Required
748
+ # * <tt>:reference_number</tt> -- transaction to reference payment from
749
+ #
750
+ # ==== Transaction Options
751
+ # * <tt>:amount</tt> -- total amount
752
+ # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number
753
+ # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters
754
+ # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters
755
+ # * <tt>:clerk</tt> -- sales clerk
756
+ # * <tt>:terminal</tt> -- terminal name
757
+ # * <tt>:table</tt> -- table name/number
758
+ # * <tt>:description</tt> -- description
759
+ # * <tt>:comments</tt> -- comments
760
+ # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+
761
+ # * <tt>:currency</tt> -- numeric currency code
762
+ # * <tt>:tax</tt> -- tax portion of amount
763
+ # * <tt>:tip</tt> -- tip portion of amount
764
+ # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable
765
+ # * <tt>:shipping</tt> -- shipping portion of amount
766
+ # * <tt>:discount</tt> -- amount of discount
767
+ # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied
768
+ #
769
+ # ==== Response
770
+ # * <tt>#message</tt> -- transaction response hash
771
+ #
772
+ def run_quick_credit(options={})
773
+ requires! options, :reference_number
774
+
775
+ request = build_request(__method__, options)
776
+ commit(__method__, request)
777
+ end
778
+
779
+ # Transaction Status ============================================
780
+
781
+ # Retrieve details of a specified transaction.
782
+ #
783
+ # ==== Required
784
+ # * <tt>:reference_number</tt>
785
+ #
786
+ # ==== Response
787
+ # * <tt>#message</tt> -- transaction hash
788
+ #
789
+ def get_transaction(options={})
790
+ requires! options, :reference_number
791
+
792
+ request = build_request(__method__, options)
793
+ commit(__method__, request)
794
+ end
795
+
796
+ # Check status of a transaction.
797
+ #
798
+ # ==== Required
799
+ # * <tt>:reference_number</tt>
800
+ #
801
+ # ==== Response
802
+ # * <tt>response.success</tt> -- success of the referenced transaction
803
+ # * <tt>response.message</tt> -- message of the referenced transaction
804
+ # * <tt>response.authorization</tt> -- same as :reference_number in options
805
+ #
806
+ def get_transaction_status(options={})
807
+ requires! options, :reference_number
808
+
809
+ request = build_request(__method__, options)
810
+ commit(__method__, request)
811
+ end
812
+
813
+ # Check status of a transaction (custom).
814
+ #
815
+ # ==== Required
816
+ # * <tt>:reference_number</tt>
817
+ # * <tt>:fields</tt> -- string array of fields to retrieve
818
+ # * <tt>Response.AuthCode</tt>
819
+ # * <tt>Response.AvsResult</tt>
820
+ # * <tt>Response.AvsResultCode</tt>
821
+ # * <tt>Response.BatchNum</tt>
822
+ # * <tt>Response.CardCodeResult</tt>
823
+ # * <tt>Response.CardCodeResultCode</tt>
824
+ # * <tt>Response.ConversionRate</tt>
825
+ # * <tt>Response.ConvertedAmount</tt>
826
+ # * <tt>Response.ConvertedAmountCurrency</tt>
827
+ # * <tt>Response.Error</tt>
828
+ # * <tt>Response.ErrorCode</tt>
829
+ # * <tt>Response.RefNum</tt>
830
+ # * <tt>Response.Result</tt>
831
+ # * <tt>Response.ResultCode</tt>
832
+ # * <tt>Response.Status</tt>
833
+ # * <tt>Response.StatusCode</tt>
834
+ # * <tt>CheckTrace.TrackingNum</tt>
835
+ # * <tt>CheckTrace.Effective</tt>
836
+ # * <tt>CheckTrace.Processed</tt>
837
+ # * <tt>CheckTrace.Settled</tt>
838
+ # * <tt>CheckTrace.Returned</tt>
839
+ # * <tt>CheckTrace.BankNote</tt>
840
+ # * <tt>DateTime</tt>
841
+ # * <tt>AccountHolder</tt>
842
+ # * <tt>Details.Invoice</tt>
843
+ # * <tt>Details.PoNum</tt>
844
+ # * <tt>Details.OrderID</tt>
845
+ # * <tt>Details.Clerk</tt>
846
+ # * <tt>Details.Terminal</tt>
847
+ # * <tt>Details.Table</tt>
848
+ # * <tt>Details.Description</tt>
849
+ # * <tt>Details.Amount</tt>
850
+ # * <tt>Details.Currency</tt>
851
+ # * <tt>Details.Tax</tt>
852
+ # * <tt>Details.Tip</tt>
853
+ # * <tt>Details.NonTax</tt>
854
+ # * <tt>Details.Shipping</tt>
855
+ # * <tt>Details.Discount</tt>
856
+ # * <tt>Details.Subtotal</tt>
857
+ # * <tt>CreditCardData.CardType</tt>
858
+ # * <tt>CreditCardData.CardNumber</tt>
859
+ # * <tt>CreditCardData.CardExpiration</tt>
860
+ # * <tt>CreditCardData.CardCode</tt>
861
+ # * <tt>CreditCardData.AvsStreet</tt>
862
+ # * <tt>CreditCardData.AvsZip</tt>
863
+ # * <tt>CreditCardData.CardPresent</tt>
864
+ # * <tt>CheckData.CheckNumber</tt>
865
+ # * <tt>CheckData.Routing</tt>
866
+ # * <tt>CheckData.Account</tt>
867
+ # * <tt>CheckData.SSN</tt>
868
+ # * <tt>CheckData.DriversLicense</tt>
869
+ # * <tt>CheckData.DriversLicenseState</tt>
870
+ # * <tt>CheckData.RecordType</tt>
871
+ # * <tt>User</tt>
872
+ # * <tt>Source</tt>
873
+ # * <tt>ServerIP</tt>
874
+ # * <tt>ClientIP</tt>
875
+ # * <tt>CustomerID</tt>
876
+ # * <tt>BillingAddress.FirstName</tt>
877
+ # * <tt>BillingAddress.LastName</tt>
878
+ # * <tt>BillingAddress.Company</tt>
879
+ # * <tt>BillingAddress.Street</tt>
880
+ # * <tt>BillingAddress.Street2</tt>
881
+ # * <tt>BillingAddress.City</tt>
882
+ # * <tt>BillingAddress.State</tt>
883
+ # * <tt>BillingAddress.Zip</tt>
884
+ # * <tt>BillingAddress.Country</tt>
885
+ # * <tt>BillingAddress.Phone</tt>
886
+ # * <tt>BillingAddress.Fax</tt>
887
+ # * <tt>BillingAddress.Email</tt>
888
+ # * <tt>ShippingAddress.FirstName</tt>
889
+ # * <tt>ShippingAddress.LastName</tt>
890
+ # * <tt>ShippingAddress.Company</tt>
891
+ # * <tt>ShippingAddress.Street</tt>
892
+ # * <tt>ShippingAddress.Street2</tt>
893
+ # * <tt>ShippingAddress.City</tt>
894
+ # * <tt>ShippingAddress.State</tt>
895
+ # * <tt>ShippingAddress.Zip</tt>
896
+ # * <tt>ShippingAddress.Country</tt>
897
+ # * <tt>ShippingAddress.Phone</tt>
898
+ # * <tt>ShippingAddress.Fax</tt>
899
+ # * <tt>ShippingAddress.Email</tt>
900
+ #
901
+ # ==== Response
902
+ # * <tt>#message</tt> -- hash; keys are the field values
903
+ #
904
+ def get_transaction_custom(options={})
905
+ requires! options, :reference_number, :fields
906
+
907
+ request = build_request(__method__, options)
908
+ commit(__method__, request)
909
+ end
910
+
911
+ # Check status of a check transaction.
912
+ #
913
+ # ==== Required
914
+ # * <tt>:reference_number</tt>
915
+ #
916
+ # ==== Response
917
+ # * <tt>#message</tt> -- check trace hash
918
+ #
919
+ def get_check_trace(options={})
920
+ requires! options, :reference_number
921
+
922
+ request = build_request(__method__, options)
923
+ commit(__method__, request)
924
+ end
925
+
926
+ # Account =======================================================
927
+
928
+ # Retrieve merchant account details
929
+ #
930
+ # ==== Response
931
+ # * <tt>#message</tt> -- account hash
932
+ #
933
+ def get_account_details
934
+ request = build_request(__method__)
935
+ commit(__method__, request)
936
+ end
937
+
938
+ # Builders ======================================================
939
+
940
+ private
941
+
942
+ # Build soap header, etc.
943
+ def build_request(action, options = {})
944
+ soap = Builder::XmlMarkup.new
945
+ soap.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
946
+ soap.tag! "SOAP-ENV:Envelope",
947
+ 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
948
+ 'xmlns:ns1' => 'urn:usaepay',
949
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
950
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
951
+ 'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
952
+ 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' do |soap|
953
+ soap.tag! "SOAP-ENV:Body" do |soap|
954
+ send("build_#{action}", soap, options)
955
+ end
956
+ end
957
+ soap.target!
958
+ end
959
+
960
+ # Build generic tag.
961
+ def build_tag(soap, type, tag, value)
962
+ soap.tag!(tag, value, 'xsi:type' => "xsd:#{type}") if value != nil
963
+ end
964
+
965
+ # Build token.
966
+ def build_token(soap, options)
967
+ seed = SecureRandom.base64(32)
968
+ hash = Digest::SHA1.hexdigest("#{@options[:login]}#{seed}#{@options[:password].to_s.strip}")
969
+ soap.Token 'xsi:type' => 'ns1:ueSecurityToken' do |soap|
970
+ build_tag soap, :string, 'ClientIP', options[:client_ip]
971
+ soap.PinHash 'xsi:type' => 'ns1:ueHash' do |soap|
972
+ build_tag soap, :string, "HashValue", hash
973
+ build_tag soap, :string, "Seed", seed
974
+ build_tag soap, :string, "Type", 'sha1'
975
+ end
976
+ build_tag soap, :string, 'SourceKey', @options[:login]
977
+ end
978
+ end
979
+
980
+ # Customer ======================================================
981
+
982
+ def build_add_customer(soap, options)
983
+ soap.tag! "ns1:addCustomer" do |soap|
984
+ build_token soap, options
985
+ build_customer_data soap, options
986
+ build_tag soap, :double, 'Amount', amount(options[:amount])
987
+ build_tag soap, :double, 'Tax', amount(options[:tax])
988
+ build_tag soap, :string, 'Next', options[:next].strftime("%Y-%m-%d") if options[:next]
989
+ end
990
+ end
991
+
992
+ def build_customer(soap, options, type, add_customer_data=false)
993
+ soap.tag! "ns1:#{type}" do |soap|
994
+ build_token soap, options
995
+ build_tag soap, :integer, 'CustNum', options[:customer_number]
996
+ build_customer_data soap, options if add_customer_data
997
+ end
998
+ end
999
+
1000
+ def build_update_customer(soap, options)
1001
+ build_customer(soap, options, 'updateCustomer', true)
1002
+ end
1003
+
1004
+ def build_enable_customer(soap, options)
1005
+ build_customer(soap, options, 'enableCustomer')
1006
+ end
1007
+
1008
+ def build_disable_customer(soap, options)
1009
+ build_customer(soap, options, 'disableCustomer')
1010
+ end
1011
+
1012
+ def build_delete_customer(soap, options)
1013
+ build_customer(soap, options, 'deleteCustomer')
1014
+ end
1015
+
1016
+ def build_add_customer_payment_method(soap, options)
1017
+ soap.tag! "ns1:addCustomerPaymentMethod" do |soap|
1018
+ build_token soap, options
1019
+ build_tag soap, :integer, 'CustNum', options[:customer_number]
1020
+ build_customer_payment_methods soap, options
1021
+ build_tag soap, :boolean, 'MakeDefault', options[:make_default]
1022
+ build_tag soap, :boolean, 'Verify', options[:verify]
1023
+ end
1024
+ end
1025
+
1026
+ def build_get_customer_payment_method(soap, options)
1027
+ soap.tag! 'ns1:getCustomerPaymentMethod' do |soap|
1028
+ build_token soap, options
1029
+ build_tag soap, :integer, 'CustNum', options[:customer_number]
1030
+ build_tag soap, :integer, 'MethodID', options[:method_id]
1031
+ end
1032
+ end
1033
+
1034
+ def build_get_customer_payment_methods(soap, options)
1035
+ build_customer(soap, options, 'getCustomerPaymentMethods')
1036
+ end
1037
+
1038
+ def build_update_customer_payment_method(soap, options)
1039
+ soap.tag! 'ns1:updateCustomerPaymentMethod' do |soap|
1040
+ build_token soap, options
1041
+ build_customer_payment_methods soap, options
1042
+ build_tag soap, :boolean, 'Verify', options[:verify]
1043
+ end
1044
+ end
1045
+
1046
+ def build_delete_customer_payment_method(soap, options)
1047
+ soap.tag! "ns1:deleteCustomerPaymentMethod" do |soap|
1048
+ build_token soap, options
1049
+ build_tag soap, :integer, 'Custnum', options[:customer_number]
1050
+ build_tag soap, :integer, 'PaymentMethodID', options[:method_id]
1051
+ end
1052
+ end
1053
+
1054
+ def build_run_customer_transaction(soap, options)
1055
+ soap.tag! "ns1:runCustomerTransaction" do |soap|
1056
+ build_token soap, options
1057
+ build_tag soap, :integer, 'CustNum', options[:customer_number]
1058
+ build_tag soap, :integer, 'PaymentMethodID', options[:method_id] || 0
1059
+ build_customer_transaction soap, options
1060
+ end
1061
+ end
1062
+
1063
+ # Transactions ==================================================
1064
+
1065
+ def build_run_transaction(soap, options)
1066
+ soap.tag! 'ns1:runTransaction' do |soap|
1067
+ build_token soap, options
1068
+ build_transaction_request_object soap, options, 'Parameters'
1069
+ end
1070
+ end
1071
+
1072
+ def build_run_sale(soap, options)
1073
+ soap.tag! 'ns1:runSale' do |soap|
1074
+ build_token soap, options
1075
+ build_transaction_request_object soap, options
1076
+ end
1077
+ end
1078
+
1079
+ def build_run_auth_only(soap, options)
1080
+ soap.tag! 'ns1:runAuthOnly' do |soap|
1081
+ build_token soap, options
1082
+ build_transaction_request_object soap, options
1083
+ end
1084
+ end
1085
+
1086
+ def build_run_credit(soap, options)
1087
+ soap.tag! 'ns1:runCredit' do |soap|
1088
+ build_token soap, options
1089
+ build_transaction_request_object soap, options
1090
+ end
1091
+ end
1092
+
1093
+ def build_run_check_sale(soap, options)
1094
+ soap.tag! 'ns1:runCheckSale' do |soap|
1095
+ build_token soap, options
1096
+ build_transaction_request_object soap, options
1097
+ end
1098
+ end
1099
+
1100
+ def build_run_check_credit(soap, options)
1101
+ soap.tag! 'ns1:runCheckCredit' do |soap|
1102
+ build_token soap, options
1103
+ build_transaction_request_object soap, options
1104
+ end
1105
+ end
1106
+
1107
+ def build_post_auth(soap, options)
1108
+ soap.tag! 'ns1:postAuth' do |soap|
1109
+ build_token soap, options
1110
+ build_transaction_request_object soap, options
1111
+ end
1112
+ end
1113
+
1114
+ def build_run_quick_sale(soap, options)
1115
+ soap.tag! 'ns1:runQuickSale' do |soap|
1116
+ build_token soap, options
1117
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1118
+ build_transaction_detail soap, options
1119
+ build_tag soap, :boolean, 'AuthOnly', options[:authorize_only] || false
1120
+ end
1121
+ end
1122
+
1123
+ def build_run_quick_credit(soap, options)
1124
+ soap.tag! 'ns1:runQuickCredit' do |soap|
1125
+ build_token soap, options
1126
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1127
+ build_transaction_detail soap, options
1128
+ end
1129
+ end
1130
+
1131
+ def build_get_transaction(soap, options)
1132
+ soap.tag! "ns1:getTransaction" do |soap|
1133
+ build_token soap, options
1134
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1135
+ end
1136
+ end
1137
+
1138
+ def build_get_transaction_status(soap, options)
1139
+ soap.tag! "ns1:getTransactionStatus" do |soap|
1140
+ build_token soap, options
1141
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1142
+ end
1143
+ end
1144
+
1145
+ def build_get_transaction_custom(soap, options)
1146
+ soap.tag! "ns1:getTransactionCustom" do |soap|
1147
+ build_token soap, options
1148
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1149
+ build_transaction_field_array soap, options
1150
+ end
1151
+ end
1152
+
1153
+ def build_get_check_trace(soap, options)
1154
+ soap.tag! "ns1:getCheckTrace" do |soap|
1155
+ build_token soap, options
1156
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1157
+ end
1158
+ end
1159
+
1160
+ def build_capture_transaction(soap, options)
1161
+ soap.tag! "ns1:captureTransaction" do |soap|
1162
+ build_token soap, options
1163
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1164
+ build_tag soap, :double, 'RefNum', amount(options[:amount])
1165
+ end
1166
+ end
1167
+
1168
+ def build_void_transaction(soap, options)
1169
+ soap.tag! "ns1:voidTransaction" do |soap|
1170
+ build_token soap, options
1171
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1172
+ end
1173
+ end
1174
+
1175
+ def build_refund_transaction(soap, options)
1176
+ soap.tag! "ns1:refundTransaction" do |soap|
1177
+ build_token soap, options
1178
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1179
+ build_tag soap, :integer, 'Amount', amount(options[:amount])
1180
+ end
1181
+ end
1182
+
1183
+ def build_override_transaction(soap, options)
1184
+ soap.tag! "ns1:overrideTransaction" do |soap|
1185
+ build_token soap, options
1186
+ build_tag soap, :integer, 'RefNum', options[:reference_number]
1187
+ build_tag soap, :string, 'Reason', options[:reason]
1188
+ end
1189
+ end
1190
+
1191
+ # Account =======================================================
1192
+
1193
+ def build_get_account_details(soap, options)
1194
+ soap.tag! "ns1:getAccountDetails" do |soap|
1195
+ build_token soap, options
1196
+ end
1197
+ end
1198
+
1199
+ # Customer Helpers ==============================================
1200
+
1201
+ def build_customer_data(soap, options)
1202
+ soap.CustomerData 'xsi:type' => 'ns1:CustomerObject' do
1203
+ CUSTOMER_OPTIONS.each do |k,v|
1204
+ build_tag soap, v[0], v[1], options[k]
1205
+ end
1206
+ build_billing_address soap, options
1207
+ build_customer_payments soap, options
1208
+ build_custom_fields soap, options
1209
+ end
1210
+ end
1211
+
1212
+ def build_customer_payments(soap, options)
1213
+ if options[:payment_methods]
1214
+ length = options[:payment_methods].length
1215
+ soap.PaymentMethods 'SOAP-ENC:arrayType' => "ns1:PaymentMethod[#{length}]",
1216
+ 'xsi:type' =>"ns1:PaymentMethodArray" do |soap|
1217
+ build_customer_payment_methods soap, options
1218
+ end
1219
+ end
1220
+ end
1221
+
1222
+ def extract_methods_and_tag(options)
1223
+ case
1224
+ when options[:payment_method] && !options[:payment_methods]
1225
+ payment_methods = [options[:payment_method]]
1226
+ tag_name = 'PaymentMethod'
1227
+ when options[:payment_methods] && !options[:payment_method]
1228
+ payment_methods = options[:payment_methods]
1229
+ tag_name = 'item'
1230
+ else
1231
+ payment_methods = [options]
1232
+ tag_name = 'PaymentMethod'
1233
+ end
1234
+ [payment_methods, tag_name]
1235
+ end
1236
+
1237
+ def build_credit_card_or_check(soap, payment_method)
1238
+ case
1239
+ when payment_method[:method].kind_of?(ActiveMerchant::Billing::CreditCard)
1240
+ build_tag soap, :string, 'CardNumber', payment_method[:method].number
1241
+ build_tag soap, :string, 'CardExpiration',
1242
+ "#{payment_method[:method].year}-#{"%02d" % payment_method[:method].month}"
1243
+ if options[:billing_address]
1244
+ build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1]
1245
+ build_tag soap, :string, 'AvsZip', options[:billing_address][:zip]
1246
+ end
1247
+ build_tag soap, :string, 'CardCode', payment_method[:method].verification_value
1248
+ when payment_method[:method].kind_of?(ActiveMerchant::Billing::Check)
1249
+ build_tag soap, :string, 'Account', payment_method[:method].number
1250
+ build_tag soap, :string, 'Routing', payment_method[:method].routing_number
1251
+ build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize
1252
+ build_tag soap, :string, 'DriversLicense', options[:drivers_license]
1253
+ build_tag soap, :string, 'DriversLicenseState', options[:drivers_license_state]
1254
+ build_tag soap, :string, 'RecordType', options[:record_type]
1255
+ end
1256
+ end
1257
+
1258
+ def build_customer_payment_methods(soap, options)
1259
+ payment_methods, tag_name = extract_methods_and_tag(options)
1260
+ payment_methods.each do |payment_method|
1261
+ soap.tag! tag_name, 'xsi:type' => "ns1:PaymentMethod" do |soap|
1262
+ build_tag soap, :integer, 'MethodID', payment_method[:method_id]
1263
+ build_tag soap, :string, 'MethodType', payment_method[:type]
1264
+ build_tag soap, :string, 'MethodName', payment_method[:name]
1265
+ build_tag soap, :integer, 'SecondarySort', payment_method[:sort]
1266
+ build_credit_card_or_check(soap, payment_method)
1267
+ end
1268
+ end
1269
+ end
1270
+
1271
+ def build_customer_transaction(soap, options)
1272
+ soap.Parameters 'xsi:type' => "ns1:CustomerTransactionRequest" do |soap|
1273
+ build_transaction_detail soap, options
1274
+ CUSTOMER_TRANSACTION_REQUEST_OPTIONS.each do |k,v|
1275
+ build_tag soap, v[0], v[1], options[k]
1276
+ end
1277
+ build_custom_fields soap, options
1278
+ build_line_items soap, options
1279
+ end
1280
+ end
1281
+
1282
+ # Transaction Helpers ===========================================
1283
+
1284
+ def build_transaction_request_object(soap, options, name='Params')
1285
+ soap.tag! name, 'xsi:type' => "ns1:TransactionRequestObject" do |soap|
1286
+ TRANSACTION_REQUEST_OBJECT_OPTIONS.each do |k,v|
1287
+ build_tag soap, v[0], v[1], options[k]
1288
+ end
1289
+ case
1290
+ when options[:payment_method] == nil
1291
+ when options[:payment_method].kind_of?(ActiveMerchant::Billing::CreditCard)
1292
+ build_credit_card_data soap, options
1293
+ when options[:payment_method].kind_of?(ActiveMerchant::Billing::Check)
1294
+ build_check_data soap, options
1295
+ else
1296
+ raise ArgumentError, 'options[:payment_method] must be a CreditCard or Check'
1297
+ end
1298
+ build_transaction_detail soap, options
1299
+ build_billing_address soap, options
1300
+ build_shipping_address soap, options
1301
+ build_recurring_billing soap, options
1302
+ build_line_items soap, options
1303
+ build_custom_fields soap, options
1304
+ end
1305
+ end
1306
+
1307
+ def build_transaction_detail(soap, options)
1308
+ soap.Details 'xsi:type' => "ns1:TransactionDetail" do |soap|
1309
+ TRANSACTION_DETAIL_OPTIONS.each do |k,v|
1310
+ build_tag soap, v[0], v[1], options[k]
1311
+ end
1312
+ TRANSACTION_DETAIL_MONEY_OPTIONS.each do |k,v|
1313
+ build_tag soap, v[0], v[1], amount(options[k])
1314
+ end
1315
+ end
1316
+ end
1317
+
1318
+ def build_credit_card_data(soap, options)
1319
+ soap.CreditCardData 'xsi:type' => "ns1:CreditCardData" do |soap|
1320
+ build_tag soap, :string, 'CardNumber', options[:payment_method].number
1321
+ build_tag soap, :string, 'CardExpiration',
1322
+ "#{options[:payment_method].year}-#{"%02d" % options[:payment_method].month}"
1323
+ if options[:billing_address]
1324
+ build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1]
1325
+ build_tag soap, :string, 'AvsZip', options[:billing_address][:zip]
1326
+ end
1327
+ build_tag soap, :string, 'CardCode', options[:payment_method].verification_value
1328
+ build_tag soap, :boolean, 'CardPresent', options[:card_present] || false
1329
+ CREDIT_CARD_DATA_OPTIONS.each do |k,v|
1330
+ build_tag soap, v[0], v[1], options[k]
1331
+ end
1332
+ end
1333
+ end
1334
+
1335
+ def build_check_data(soap, options)
1336
+ soap.CheckData 'xsi:type' => "ns1:CheckData" do |soap|
1337
+ build_tag soap, :string, 'Account', options[:payment_method].number
1338
+ build_tag soap, :string, 'Routing', options[:payment_method].routing_number
1339
+ build_tag soap, :string, 'AccountType', options[:payment_method].account_type.capitalize
1340
+ CHECK_DATA_OPTIONS.each do |k,v|
1341
+ build_tag soap, v[0], v[1], options[k]
1342
+ end
1343
+ end
1344
+ end
1345
+
1346
+ def build_recurring_billing(soap, options)
1347
+ if options[:recurring]
1348
+ soap.RecurringBilling 'xsi:type' => "ns1:RecurringBilling" do |soap|
1349
+ build_tag soap, :double, 'Amount', amount(options[:recurring][:amount])
1350
+ build_tag soap, :string, 'Next', options[:recurring][:next].strftime("%Y-%m-%d") if options[:recurring][:next]
1351
+ build_tag soap, :string, 'Expire', options[:recurring][:expire].strftime("%Y-%m-%d") if options[:recurring][:expire]
1352
+ RECURRING_BILLING_OPTIONS.each do |k,v|
1353
+ build_tag soap, v[0], v[1], options[:recurring][k]
1354
+ end
1355
+ end
1356
+ end
1357
+ end
1358
+
1359
+ def build_transaction_field_array(soap, options)
1360
+ soap.Fields 'SOAP-ENC:arryType' => "xsd:string[#{options[:fields].length}]", 'xsi:type' => 'ns1:stringArray' do |soap|
1361
+ options[:fields].each do |field|
1362
+ build_tag soap, :string, 'item', field
1363
+ end
1364
+ end
1365
+ end
1366
+
1367
+ # General Helpers ===============================================
1368
+
1369
+ def build_billing_address(soap, options)
1370
+ if options[:billing_address]
1371
+ if options[:billing_address][:name]
1372
+ name = options[:billing_address][:name].split(nil,2) # divide name
1373
+ options[:billing_address][:first_name], options[:billing_address][:last_name] = name[0], name[1]
1374
+ end
1375
+ soap.BillingAddress 'xsi:type' => "ns1:Address" do
1376
+ ADDRESS_OPTIONS.each do |k,v|
1377
+ build_tag soap, v[0], v[1], options[:billing_address][k]
1378
+ end
1379
+ end
1380
+ end
1381
+ end
1382
+
1383
+ def build_shipping_address(soap, options)
1384
+ if options[:shipping_address]
1385
+ if options[:shipping_address][:name]
1386
+ name = options[:shipping_address][:name].split(nil,2) # divide name
1387
+ options[:shipping_address][:first_name], options[:shipping_address][:last_name] = name[0], name[1]
1388
+ end
1389
+ soap.ShippingAddress 'xsi:type' => "ns1:Address" do
1390
+ ADDRESS_OPTIONS.each do |k,v|
1391
+ build_tag soap, v[0], v[1], options[:shipping_address][k]
1392
+ end
1393
+ end
1394
+ end
1395
+ end
1396
+
1397
+ def build_line_items(soap, options) # TODO
1398
+ end
1399
+
1400
+ def build_custom_fields(soap, options) # TODO
1401
+ end
1402
+
1403
+ # Request =======================================================
1404
+
1405
+ def commit(action, request)
1406
+ url = test? ? test_url : live_url
1407
+
1408
+ begin
1409
+ soap = ssl_post(url, request, "Content-Type" => "text/xml")
1410
+ rescue ActiveMerchant::ResponseError => error
1411
+ soap = error.response.body
1412
+ end
1413
+
1414
+ response = build_response(action, soap)
1415
+ end
1416
+
1417
+ def build_response(action, soap)
1418
+ response_params, success, message, authorization, avs, cvv = parse(action, soap)
1419
+
1420
+ response_params.merge!('soap_response' => soap) if @options[:soap_response]
1421
+
1422
+ response = Response.new(
1423
+ success, message, response_params,
1424
+ :test => test?, :authorization => authorization,
1425
+ :avs_result => avs_from(avs),
1426
+ :cvv_result => cvv
1427
+ )
1428
+ end
1429
+
1430
+ def avs_from(avs)
1431
+ avs_params = { :code => avs }
1432
+ avs_params.merge!(:message => AVS_CUSTOM_MESSAGES[avs]) if AVS_CUSTOM_MESSAGES.key?(avs)
1433
+ avs_params
1434
+ end
1435
+
1436
+ def parse(action, soap)
1437
+ xml = REXML::Document.new(soap)
1438
+ root = REXML::XPath.first(xml, "//SOAP-ENV:Body")
1439
+ response = root ? parse_element(root[0]) : { :response => soap }
1440
+
1441
+ success, message, authorization, avs, cvv = false, FAILURE_MESSAGE, nil, nil, nil
1442
+
1443
+ fault = (!response) || (response.length < 1) || response.has_key?('faultcode')
1444
+ return [response, success, response['faultstring'], authorization, avs, cvv] if fault
1445
+
1446
+ if response.respond_to?(:[]) && p = response["#{action}_return"]
1447
+ if p.respond_to?(:key?) && p.key?('result_code')
1448
+ success = p['result_code'] == 'A' ? true : false
1449
+ authorization = p['ref_num']
1450
+ avs = AVS_RESULTS[p['avs_result_code']]
1451
+ cvv = p['card_code_result_code']
1452
+ else
1453
+ success = true
1454
+ end
1455
+ message = case action
1456
+ when :get_customer_payment_methods
1457
+ p['item']
1458
+ when :get_transaction_custom
1459
+ p['item'].inject({}) { |map, field| map[field['field']] = field['value']; map }
1460
+ else
1461
+ p
1462
+ end
1463
+ elsif response.respond_to?(:[]) && p = response[:response]
1464
+ message = p # when response is html
1465
+ end
1466
+
1467
+ [response, success, message, authorization, avs, cvv]
1468
+ end
1469
+
1470
+ def parse_element(node)
1471
+ if node.has_elements?
1472
+ response = {}
1473
+ node.elements.each do |e|
1474
+ key = e.name.underscore
1475
+ value = parse_element(e)
1476
+ if response.has_key?(key)
1477
+ if response[key].is_a?(Array)
1478
+ response[key].push(value)
1479
+ else
1480
+ response[key] = [response[key], value]
1481
+ end
1482
+ else
1483
+ response[key] = parse_element(e)
1484
+ end
1485
+ end
1486
+ else
1487
+ response = node.text
1488
+ end
1489
+
1490
+ response
1491
+ end
1492
+
1493
+ end
1494
+ end
1495
+ end
1496
+