activemerchant 1.29.1 → 1.30.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 (50) hide show
  1. data/CHANGELOG +48 -0
  2. data/CONTRIBUTORS +19 -0
  3. data/README.md +43 -41
  4. data/lib/active_merchant/billing/check.rb +15 -11
  5. data/lib/active_merchant/billing/credit_card.rb +5 -1
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +8 -8
  7. data/lib/active_merchant/billing/gateway.rb +1 -1
  8. data/lib/active_merchant/billing/gateways/authorize_net.rb +9 -1
  9. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +15 -4
  10. data/lib/active_merchant/billing/gateways/balanced.rb +9 -3
  11. data/lib/active_merchant/billing/gateways/banwire.rb +15 -1
  12. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +6 -2
  13. data/lib/active_merchant/billing/gateways/beanstream.rb +26 -24
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +5 -2
  15. data/lib/active_merchant/billing/gateways/cyber_source.rb +55 -22
  16. data/lib/active_merchant/billing/gateways/eway.rb +114 -171
  17. data/lib/active_merchant/billing/gateways/eway_managed.rb +52 -22
  18. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +222 -0
  19. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +13 -2
  20. data/lib/active_merchant/billing/gateways/litle.rb +50 -19
  21. data/lib/active_merchant/billing/gateways/merchant_ware.rb +44 -9
  22. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +190 -0
  23. data/lib/active_merchant/billing/gateways/moneris.rb +3 -5
  24. data/lib/active_merchant/billing/gateways/moneris_us.rb +1 -1
  25. data/lib/active_merchant/billing/gateways/nab_transact.rb +20 -3
  26. data/lib/active_merchant/billing/gateways/netbilling.rb +1 -0
  27. data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
  28. data/lib/active_merchant/billing/gateways/optimal_payment.rb +18 -3
  29. data/lib/active_merchant/billing/gateways/orbital.rb +9 -5
  30. data/lib/active_merchant/billing/gateways/payment_express.rb +62 -1
  31. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
  32. data/lib/active_merchant/billing/gateways/paypal_express.rb +2 -0
  33. data/lib/active_merchant/billing/gateways/pin.rb +157 -0
  34. data/lib/active_merchant/billing/gateways/qbms.rb +3 -2
  35. data/lib/active_merchant/billing/gateways/quickpay.rb +66 -28
  36. data/lib/active_merchant/billing/gateways/sage_pay.rb +6 -0
  37. data/lib/active_merchant/billing/gateways/smart_ps.rb +1 -1
  38. data/lib/active_merchant/billing/gateways/spreedly_core.rb +235 -0
  39. data/lib/active_merchant/billing/gateways/stripe.rb +1 -0
  40. data/lib/active_merchant/billing/gateways/wirecard.rb +15 -9
  41. data/lib/active_merchant/billing/gateways/worldpay.rb +15 -4
  42. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +4 -1
  43. data/lib/active_merchant/billing/integrations/paypal/notification.rb +39 -31
  44. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +13 -10
  45. data/lib/active_merchant/billing/integrations/quickpay/notification.rb +14 -14
  46. data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +2 -2
  47. data/lib/active_merchant/version.rb +1 -1
  48. data.tar.gz.sig +0 -0
  49. metadata +109 -49
  50. metadata.gz.sig +0 -0
@@ -0,0 +1,223 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ #
4
+ # NETPAY Gateway
5
+ #
6
+ # Support for NETPAY's HTTP Connector payment gateway in Mexico.
7
+ #
8
+ # The gateway sends requests as HTTP POST and receives the response details
9
+ # in the HTTP header, making the process really rather simple.
10
+ #
11
+ # Support for calls to the authorize and capture methods have been included
12
+ # as per the Netpay manuals, however, your millage may vary with these
13
+ # methods. At the time of writing (January 2013) they were not fully
14
+ # supported by the production or test gateways. This situation is
15
+ # expected to change within a few weeks/months.
16
+ #
17
+ # Purchases can be cancelled (`#void`) only within 24 hours of the
18
+ # transaction. After this, a refund should be performed instead.
19
+ #
20
+ # In addition to the regular ActiveMerchant transaction options, NETPAY
21
+ # also supports a `:mode` parameter. This allows testing to be peformed
22
+ # in production and force specific results.
23
+ #
24
+ # * 'P' - Production
25
+ # * 'A' - Approved
26
+ # * 'D' - Declined
27
+ # * 'R' - Random (Approved or Declined)
28
+ # * 'T' - Test
29
+ #
30
+ # For example:
31
+ #
32
+ # response = @gateway.purchase(1000, card, :mode => 'D')
33
+ # response.success # false
34
+ #
35
+ class NetpayGateway < Gateway
36
+ self.test_url = 'http://200.57.87.243:8855'
37
+ self.live_url = 'https://suite.netpay.com.mx/acquirerprd'
38
+
39
+ # The countries the gateway supports merchants from as 2 digit ISO country codes
40
+ self.supported_countries = ['MX']
41
+
42
+ self.default_currency = 'MXN'
43
+
44
+ # The card types supported by the payment gateway
45
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
46
+
47
+ # The homepage URL of the gateway
48
+ self.homepage_url = 'http://www.netpay.com.mx'
49
+
50
+ # The name of the gateway
51
+ self.display_name = 'NETPAY Gateway'
52
+
53
+ CURRENCY_CODES = {
54
+ "MXN" => '484'
55
+ }
56
+
57
+ # The header keys that we will provide in the response params hash
58
+ RESPONSE_KEYS = ['ResponseMsg', 'ResponseText', 'ResponseCode', 'TimeIn', 'TimeOut', 'AuthCode', 'OrderId', 'CardTypeName', 'MerchantId', 'IssuerAuthDate']
59
+
60
+ def initialize(options = {})
61
+ requires!(options, :store_id, :login, :password)
62
+ super
63
+ end
64
+
65
+ # Send an authorization request
66
+ def authorize(money, creditcard, options = {})
67
+ post = {}
68
+ add_invoice(post, options)
69
+ add_creditcard(post, creditcard)
70
+ add_customer_data(post, options)
71
+ add_amount(post, money, options)
72
+
73
+ commit('PreAuth', post, options)
74
+ end
75
+
76
+ # Capture an authorization
77
+ def capture(money, authorization, options = {})
78
+ post = {}
79
+ add_order_id(post, order_id_from(authorization))
80
+ add_amount(post, money, options)
81
+
82
+ commit('PostAuth', post, options)
83
+ end
84
+
85
+ # Cancel an auth/purchase within first 24 hours
86
+ def void(authorization, options = {})
87
+ post = {}
88
+ order_id, amount, currency = split_authorization(authorization)
89
+ add_order_id(post, order_id)
90
+ post['Total'] = (options[:amount] || amount)
91
+ post['CurrencyCode'] = currency
92
+
93
+ commit('Refund', post, options)
94
+ end
95
+
96
+ # Make a purchase.
97
+ def purchase(money, creditcard, options = {})
98
+ post = {}
99
+ add_invoice(post, options)
100
+ add_creditcard(post, creditcard)
101
+ add_customer_data(post, options)
102
+ add_amount(post, money, options)
103
+
104
+ commit('Auth', post, options)
105
+ end
106
+
107
+ # Perform a Credit transaction.
108
+ def refund(money, authorization, options = {})
109
+ post = {}
110
+ add_order_id(post, order_id_from(authorization))
111
+ add_amount(post, money, options)
112
+
113
+ #commit('Refund', post, options)
114
+ commit('Credit', post, options)
115
+ end
116
+
117
+ private
118
+
119
+ def add_login_data(post)
120
+ post['StoreId'] = @options[:store_id]
121
+ post['UserName'] = @options[:login]
122
+ post['Password'] = @options[:password]
123
+ end
124
+
125
+ def add_action(post, action, options)
126
+ post['ResourceName'] = action
127
+ post['ContentType'] = 'Transaction'
128
+ post['Mode'] = options[:mode] || 'P'
129
+ end
130
+
131
+ def add_order_id(post, order_id)
132
+ post['OrderId'] = order_id
133
+ end
134
+
135
+ def add_amount(post, money, options)
136
+ post['Total'] = amount(money)
137
+ post['CurrencyCode'] = currency_code(options[:currency] || currency(money))
138
+ end
139
+
140
+ def add_customer_data(post, options)
141
+ post['IPAddress'] = options[:ip] unless options[:ip].blank?
142
+ end
143
+
144
+ def add_invoice(post, options)
145
+ post['Comments'] = options[:description] if options[:description]
146
+ end
147
+
148
+ def add_creditcard(post, creditcard)
149
+ post['CardNumber'] = creditcard.number
150
+ post['ExpDate'] = expdate(creditcard)
151
+ post['CustomerName'] = creditcard.name
152
+ post['CVV2'] = creditcard.verification_value unless creditcard.verification_value.nil?
153
+ end
154
+
155
+ def build_authorization(request_params, response_params)
156
+ [response_params['OrderId'], request_params['Total'], request_params['CurrencyCode']].join('|')
157
+ end
158
+
159
+ def split_authorization(authorization)
160
+ order_id, amount, currency = authorization.split("|")
161
+ [order_id, amount, currency]
162
+ end
163
+
164
+ def order_id_from(authorization)
165
+ split_authorization(authorization).first
166
+ end
167
+
168
+ def expdate(credit_card)
169
+ year = sprintf("%.4i", credit_card.year)
170
+ month = sprintf("%.2i", credit_card.month)
171
+
172
+ "#{month}/#{year[-2..-1]}"
173
+ end
174
+
175
+ def url
176
+ test? ? test_url : live_url
177
+ end
178
+
179
+ def parse(response, request_params)
180
+ response_params = params_from_response(response)
181
+
182
+ success = (response_params['ResponseCode'] == '00')
183
+ message = response_params['ResponseText'] || response_params['ResponseMsg']
184
+ options = @options.merge(:test => test?,
185
+ :authorization => build_authorization(request_params, response_params))
186
+
187
+ Response.new(success, message, response_params, options)
188
+ end
189
+
190
+ def commit(action, parameters, options)
191
+ add_login_data(parameters)
192
+ add_action(parameters, action, options)
193
+
194
+ post = parameters.collect{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
195
+ parse(ssl_post(url, post), parameters)
196
+ end
197
+
198
+ # Override the regular handle response so we can access the headers
199
+ def handle_response(response)
200
+ case response.code.to_i
201
+ when 200...300
202
+ response
203
+ else
204
+ raise ResponseError.new(response)
205
+ end
206
+ end
207
+
208
+ # Return a hash containing all the useful, or informative values from netpay
209
+ def params_from_response(response)
210
+ params = {}
211
+ RESPONSE_KEYS.each do |k|
212
+ params[k] = response[k] unless response[k].to_s.empty?
213
+ end
214
+ params
215
+ end
216
+
217
+ def currency_code(currency)
218
+ return currency if currency =~ /^\d+$/
219
+ CURRENCY_CODES[currency]
220
+ end
221
+ end
222
+ end
223
+ end
@@ -90,7 +90,9 @@ module ActiveMerchant #:nodoc:
90
90
 
91
91
  Response.new(successful?(response), message_from(response), hash_from_xml(response),
92
92
  :test => test?,
93
- :authorization => authorization_from(response)
93
+ :authorization => authorization_from(response),
94
+ :avs_result => { :code => avs_result_from(response) },
95
+ :cvv_result => cvv_result_from(response)
94
96
  )
95
97
  end
96
98
 
@@ -108,7 +110,15 @@ module ActiveMerchant #:nodoc:
108
110
  end
109
111
 
110
112
  def authorization_from(response)
111
- REXML::XPath.first(response, '//confirmationNumber').text rescue nil
113
+ get_text_from_document(response, '//confirmationNumber')
114
+ end
115
+
116
+ def avs_result_from(response)
117
+ get_text_from_document(response, '//avsResponse')
118
+ end
119
+
120
+ def cvv_result_from(response)
121
+ get_text_from_document(response, '//cvdResponse')
112
122
  end
113
123
 
114
124
  def hash_from_xml(response)
@@ -138,6 +148,11 @@ module ActiveMerchant #:nodoc:
138
148
  xml.target!
139
149
  end
140
150
 
151
+ def get_text_from_document(document, node)
152
+ node = REXML::XPath.first(document, node)
153
+ node && node.text
154
+ end
155
+
141
156
  def cc_auth_request(money, opts)
142
157
  xml_document('ccAuthRequestV1') do |xml|
143
158
  build_merchant_account(xml, @options)
@@ -251,7 +266,7 @@ module ActiveMerchant #:nodoc:
251
266
  xml.tag! 'country', CGI.escape(addr[:country] ) if addr[:country].present?
252
267
  xml.tag! 'zip' , CGI.escape(addr[:zip] ) # this one's actually required
253
268
  xml.tag! 'phone' , CGI.escape(addr[:phone] ) if addr[:phone].present?
254
- #xml.tag! 'email' , ''
269
+ xml.tag! 'email', CGI.escape(opts[:email]) if opts[:email]
255
270
  end
256
271
  end
257
272
 
@@ -125,9 +125,13 @@ module ActiveMerchant #:nodoc:
125
125
  refund(money, authorization, options)
126
126
  end
127
127
 
128
- # setting money to nil will perform a full void
129
- def void(money, authorization, options = {})
130
- order = build_void_request_xml(money, authorization, options)
128
+ def void(authorization, options = {}, deprecated = {})
129
+ if(!options.kind_of?(Hash))
130
+ deprecated("Calling the void method with an amount parameter is deprecated and will be removed in a future version.")
131
+ return void(options, deprecated.merge(:amount => authorization))
132
+ end
133
+
134
+ order = build_void_request_xml(authorization, options)
131
135
  commit(order, :void)
132
136
  end
133
137
 
@@ -396,7 +400,7 @@ module ActiveMerchant #:nodoc:
396
400
  xml.target!
397
401
  end
398
402
 
399
- def build_void_request_xml(money, authorization, parameters = {})
403
+ def build_void_request_xml(authorization, parameters = {})
400
404
  tx_ref_num, order_id = split_authorization(authorization)
401
405
  xml = xml_envelope
402
406
  xml.tag! :Request do
@@ -404,7 +408,7 @@ module ActiveMerchant #:nodoc:
404
408
  add_xml_credentials(xml)
405
409
  xml.tag! :TxRefNum, tx_ref_num
406
410
  xml.tag! :TxRefIdx, parameters[:transaction_index]
407
- xml.tag! :AdjustedAmt, amount(money)
411
+ xml.tag! :AdjustedAmt, parameters[:amount] # setting adjusted amount to nil will void entire amount
408
412
  xml.tag! :OrderID, format_order_id(order_id || parameters[:order_id])
409
413
  add_bin_merchant_and_terminal(xml, parameters)
410
414
  end
@@ -140,6 +140,7 @@ module ActiveMerchant #:nodoc:
140
140
  add_amount(result, money, options)
141
141
  add_invoice(result, options)
142
142
  add_address_verification_data(result, options)
143
+ add_optional_elements(result, options)
143
144
  result
144
145
  end
145
146
 
@@ -149,6 +150,7 @@ module ActiveMerchant #:nodoc:
149
150
  add_amount(result, money, options)
150
151
  add_invoice(result, options)
151
152
  add_reference(result, identification)
153
+ add_optional_elements(result, options)
152
154
  result
153
155
  end
154
156
 
@@ -157,6 +159,7 @@ module ActiveMerchant #:nodoc:
157
159
  add_credit_card(result, credit_card)
158
160
  add_amount(result, 100, options) #need to make an auth request for $1
159
161
  add_token_request(result, options)
162
+ add_optional_elements(result, options)
160
163
  result
161
164
  end
162
165
 
@@ -209,7 +212,7 @@ module ActiveMerchant #:nodoc:
209
212
 
210
213
  def add_invoice(xml, options)
211
214
  xml.add_element("TxnId").text = options[:order_id].to_s.slice(0, 16) unless options[:order_id].blank?
212
- xml.add_element("MerchantReference").text = options[:description] unless options[:description].blank?
215
+ xml.add_element("MerchantReference").text = options[:description].to_s.slice(0, 50) unless options[:description].blank?
213
216
  end
214
217
 
215
218
  def add_address_verification_data(xml, options)
@@ -223,6 +226,52 @@ module ActiveMerchant #:nodoc:
223
226
  xml.add_element("AvsPostCode").text = address[:zip]
224
227
  end
225
228
 
229
+ # The options hash may contain optional data which will be passed
230
+ # through the the specialized optional fields at PaymentExpress
231
+ # as follows:
232
+ #
233
+ # {
234
+ # :client_type => :web, # Possible values are: :web, :ivr, :moto, :unattended, :internet, or :recurring
235
+ # :txn_data1 => "String up to 255 characters",
236
+ # :txn_data2 => "String up to 255 characters",
237
+ # :txn_data3 => "String up to 255 characters"
238
+ # }
239
+ #
240
+ # +:client_type+, while not documented for PxPost, will be sent as
241
+ # the +ClientType+ XML element as described in the documentation for
242
+ # the PaymentExpress WebService: http://www.paymentexpress.com/Technical_Resources/Ecommerce_NonHosted/WebService#clientType
243
+ # (PaymentExpress have confirmed that this value works the same in PxPost).
244
+ # The value sent for +:client_type+ will be normalized and sent
245
+ # as one of the explicit values allowed by PxPost:
246
+ #
247
+ # :web => "Web"
248
+ # :ivr => "IVR"
249
+ # :moto => "MOTO"
250
+ # :unattended => "Unattended"
251
+ # :internet => "Internet"
252
+ # :recurring => "Recurring"
253
+ #
254
+ # If you set the +:client_type+ to any value not listed above,
255
+ # the ClientType element WILL NOT BE INCLUDED at all in the
256
+ # POST data.
257
+ #
258
+ # +:txn_data1+, +:txn_data2+, and +:txn_data3+ will be sent as
259
+ # +TxnData1+, +TxnData2+, and +TxnData3+, respectively, and are
260
+ # free form fields of the merchant's choosing, as documented here:
261
+ # http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#txndata
262
+ #
263
+ # These optional elements are added to all transaction types:
264
+ # +purchase+, +authorize+, +capture+, +refund+, +store+
265
+ def add_optional_elements(xml, options)
266
+ if client_type = normalized_client_type(options[:client_type])
267
+ xml.add_element("ClientType").text = client_type
268
+ end
269
+
270
+ xml.add_element("TxnData1").text = options[:txn_data1].to_s.slice(0,255) unless options[:txn_data1].blank?
271
+ xml.add_element("TxnData2").text = options[:txn_data2].to_s.slice(0,255) unless options[:txn_data2].blank?
272
+ xml.add_element("TxnData3").text = options[:txn_data3].to_s.slice(0,255) unless options[:txn_data3].blank?
273
+ end
274
+
226
275
  def new_transaction
227
276
  REXML::Document.new.add_element("Txn")
228
277
  end
@@ -266,6 +315,18 @@ module ActiveMerchant #:nodoc:
266
315
  def format_date(month, year)
267
316
  "#{format(month, :two_digits)}#{format(year, :two_digits)}"
268
317
  end
318
+
319
+ def normalized_client_type(client_type_from_options)
320
+ case client_type_from_options.to_s.downcase
321
+ when 'web' then "Web"
322
+ when 'ivr' then "IVR"
323
+ when 'moto' then "MOTO"
324
+ when 'unattended' then "Unattended"
325
+ when 'internet' then "Internet"
326
+ when 'recurring' then "Recurring"
327
+ else nil
328
+ end
329
+ end
269
330
  end
270
331
 
271
332
  class PaymentExpressResponse < Response
@@ -397,7 +397,7 @@ module ActiveMerchant #:nodoc:
397
397
  transaction_search_optional_fields = %w{ Payer ReceiptID Receiver
398
398
  TransactionID InvoiceID CardNumber
399
399
  AuctionItemNumber TransactionClass
400
- CurrencyCode Status }
400
+ CurrencyCode Status ProfileID }
401
401
  build_request_wrapper('TransactionSearch') do |xml|
402
402
  xml.tag! 'StartDate', date_to_iso(options[:start_date])
403
403
  xml.tag! 'EndDate', date_to_iso(options[:end_date]) unless options[:end_date].blank?
@@ -1,5 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/paypal/paypal_common_api'
2
2
  require File.dirname(__FILE__) + '/paypal/paypal_express_response'
3
+ require File.dirname(__FILE__) + '/paypal/paypal_recurring_api'
3
4
  require File.dirname(__FILE__) + '/paypal_express_common'
4
5
 
5
6
  module ActiveMerchant #:nodoc:
@@ -7,6 +8,7 @@ module ActiveMerchant #:nodoc:
7
8
  class PaypalExpressGateway < Gateway
8
9
  include PaypalCommonAPI
9
10
  include PaypalExpressCommon
11
+ include PaypalRecurringApi
10
12
 
11
13
  NON_STANDARD_LOCALE_CODES = {
12
14
  'DK' => 'da_DK',
@@ -0,0 +1,157 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class PinGateway < Gateway
4
+ self.test_url = 'https://test-api.pin.net.au/1'
5
+ self.live_url = 'https://api.pin.net.au/1'
6
+
7
+ self.default_currency = 'AUD'
8
+ self.money_format = :cents
9
+ self.supported_countries = ['AU']
10
+ self.supported_cardtypes = [:visa, :master]
11
+ self.homepage_url = 'http://www.pin.net.au/'
12
+ self.display_name = 'Pin'
13
+
14
+ def initialize(options = {})
15
+ requires!(options, :api_key)
16
+ super
17
+ end
18
+
19
+ # Create a charge using a credit card, card token or customer token
20
+ #
21
+ # To charge a credit card: purchase([money], [creditcard hash], ...)
22
+ # To charge a customer: purchase([money], [token], ...)
23
+ def purchase(money, creditcard, options = {})
24
+ post = {}
25
+
26
+ add_amount(post, money, options)
27
+ add_customer_data(post, options)
28
+ add_invoice(post, options)
29
+ add_creditcard(post, creditcard)
30
+ add_address(post, creditcard, options)
31
+
32
+ commit('charges', post)
33
+ end
34
+
35
+ # Create a customer and associated credit card. The token that is returned
36
+ # can be used instead of a credit card parameter in the purchase method
37
+ def store(creditcard, options = {})
38
+ post = {}
39
+
40
+ add_creditcard(post, creditcard)
41
+ add_customer_data(post, options)
42
+ add_address(post, creditcard, options)
43
+ commit('customers', post)
44
+ end
45
+
46
+ # Refund a transaction, note that the money attribute is ignored at the
47
+ # moment as the API does not support partial refunds. The parameter is
48
+ # kept for compatibility reasons
49
+ def refund(money, token, options = {})
50
+ commit("charges/#{CGI.escape(token)}/refunds", :amount => amount(money))
51
+ end
52
+
53
+ private
54
+ def add_amount(post, money, options)
55
+ post[:amount] = amount(money)
56
+ post[:currency] = (options[:currency] || currency(money))
57
+ post[:currency] = post[:currency].upcase if post[:currency]
58
+ end
59
+
60
+ def add_customer_data(post, options)
61
+ post[:email] = options[:email]
62
+ post[:ip_address] = options[:ip]
63
+ end
64
+
65
+ def add_address(post, creditcard, options)
66
+ return if creditcard.kind_of?(String)
67
+ address = (options[:billing_address] || options[:address])
68
+ return unless address
69
+
70
+ post[:card] ||= {}
71
+ post[:card].merge!(
72
+ :address_line1 => address[:address1],
73
+ :address_city => address[:city],
74
+ :address_postcode => address[:zip],
75
+ :address_state => address[:state],
76
+ :address_country => address[:country]
77
+ )
78
+ end
79
+
80
+ def add_invoice(post, options)
81
+ post[:description] = options[:description]
82
+ end
83
+
84
+ def add_creditcard(post, creditcard)
85
+ if creditcard.respond_to?(:number)
86
+ post[:card] ||= {}
87
+
88
+ post[:card].merge!(
89
+ :number => creditcard.number,
90
+ :expiry_month => creditcard.month,
91
+ :expiry_year => creditcard.year,
92
+ :cvc => creditcard.verification_value,
93
+ :name => "#{creditcard.first_name} #{creditcard.last_name}"
94
+ )
95
+ elsif creditcard.kind_of?(String)
96
+ post[:customer_token] = creditcard
97
+ end
98
+ end
99
+
100
+ def headers
101
+ {
102
+ "Content-Type" => "application/json",
103
+ "Authorization" => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}"
104
+ }
105
+ end
106
+
107
+ def commit(action, params)
108
+ url = "#{test? ? test_url : live_url}/#{action}"
109
+
110
+ begin
111
+ body = parse(ssl_post(url, post_data(params), headers))
112
+ rescue ResponseError => e
113
+ body = parse(e.response.body)
114
+ end
115
+
116
+ if body["response"]
117
+ success_response(body)
118
+ elsif body["error"]
119
+ error_response(body)
120
+ end
121
+ end
122
+
123
+ def success_response(body)
124
+ response = body["response"]
125
+ Response.new(
126
+ true,
127
+ response['status_message'],
128
+ body,
129
+ :authorization => token(response),
130
+ :test => test?
131
+ )
132
+ end
133
+
134
+ def error_response(body)
135
+ Response.new(
136
+ false,
137
+ body['error_description'],
138
+ body,
139
+ :authorization => nil,
140
+ :test => test?
141
+ )
142
+ end
143
+
144
+ def token(response)
145
+ response['token']
146
+ end
147
+
148
+ def parse(body)
149
+ JSON.parse(body)
150
+ end
151
+
152
+ def post_data(parameters = {})
153
+ parameters.to_json
154
+ end
155
+ end
156
+ end
157
+ end
@@ -126,6 +126,7 @@ module ActiveMerchant #:nodoc:
126
126
  parameters[:trans_request_id] ||= SecureRandom.hex(10)
127
127
 
128
128
  req = build_request(type, money, parameters)
129
+
129
130
  data = ssl_post(url, req, "Content-Type" => "application/x-qbmsxml")
130
131
  response = parse(type, data)
131
132
  message = (response[:status_message] || '').strip
@@ -260,8 +261,8 @@ module ActiveMerchant #:nodoc:
260
261
 
261
262
  def add_address(xml, parameters)
262
263
  if address = parameters[:billing_address] || parameters[:address]
263
- xml.tag!("CreditCardAddress", address[:address1][0...30])
264
- xml.tag!("CreditCardPostalCode", address[:zip][0...9])
264
+ xml.tag!("CreditCardAddress", (address[:address1] || "")[0...30])
265
+ xml.tag!("CreditCardPostalCode", (address[:zip] || "")[0...9])
265
266
  end
266
267
  end
267
268