activemerchant 1.18.1 → 1.20.0

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