activemerchant 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. data.tar.gz.sig +3 -1
  2. data/CHANGELOG +17 -0
  3. data/CONTRIBUTORS +16 -0
  4. data/README.rdoc +5 -0
  5. data/lib/active_merchant.rb +0 -1
  6. data/lib/active_merchant/billing/gateway.rb +1 -1
  7. data/lib/active_merchant/billing/gateways/authorize_net.rb +5 -2
  8. data/lib/active_merchant/billing/gateways/federated_canada.rb +169 -0
  9. data/lib/active_merchant/billing/gateways/garanti.rb +13 -1
  10. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +4 -4
  11. data/lib/active_merchant/billing/gateways/nmi.rb +13 -0
  12. data/lib/active_merchant/billing/gateways/pay_junction.rb +8 -8
  13. data/lib/active_merchant/billing/gateways/paybox_direct.rb +3 -4
  14. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +2 -2
  15. data/lib/active_merchant/billing/gateways/paypal_express.rb +37 -5
  16. data/lib/active_merchant/billing/gateways/qbms.rb +290 -0
  17. data/lib/active_merchant/billing/integrations/direc_pay/helper.rb +25 -21
  18. data/lib/active_merchant/billing/integrations/moneybookers.rb +23 -0
  19. data/lib/active_merchant/billing/integrations/moneybookers/helper.rb +33 -0
  20. data/lib/active_merchant/billing/integrations/moneybookers/notification.rb +101 -0
  21. data/lib/active_merchant/billing/integrations/valitor/helper.rb +5 -1
  22. data/lib/active_merchant/billing/integrations/world_pay.rb +34 -0
  23. data/lib/active_merchant/billing/integrations/world_pay/helper.rb +100 -0
  24. data/lib/active_merchant/billing/integrations/world_pay/notification.rb +160 -0
  25. data/lib/active_merchant/version.rb +1 -1
  26. metadata +13 -4
  27. metadata.gz.sig +0 -0
@@ -78,6 +78,8 @@ module ActiveMerchant #:nodoc:
78
78
 
79
79
  xml.tag! 'n2:NotifyURL', options[:notify_url]
80
80
  xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
81
+ xml.tag! 'n2:InvoiceID', options[:order_id]
82
+ xml.tag! 'n2:OrderDescription', options[:description]
81
83
  end
82
84
  end
83
85
  end
@@ -94,20 +96,50 @@ module ActiveMerchant #:nodoc:
94
96
  xml.tag! 'SetExpressCheckoutRequest', 'xmlns:n2' => EBAY_NAMESPACE do
95
97
  xml.tag! 'n2:Version', API_VERSION
96
98
  xml.tag! 'n2:SetExpressCheckoutRequestDetails' do
97
- xml.tag! 'n2:PaymentAction', action
98
- xml.tag! 'n2:OrderTotal', amount(money).to_f.zero? ? localized_amount(100, currency_code) : localized_amount(money, currency_code), 'currencyID' => currency_code
99
99
  if options[:max_amount]
100
100
  xml.tag! 'n2:MaxAmount', localized_amount(options[:max_amount], currency_code), 'currencyID' => currency_code
101
101
  end
102
+ if !options[:allow_note].nil?
103
+ xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0'
104
+ end
105
+ xml.tag! 'n2:PaymentDetails' do
106
+ xml.tag! 'n2:OrderTotal', amount(money).to_f.zero? ? localized_amount(100, currency_code) : localized_amount(money, currency_code), 'currencyID' => currency_code
107
+ # All of the values must be included together and add up to the order total
108
+ if [:subtotal, :shipping, :handling, :tax].all? { |o| options.has_key?(o) }
109
+ xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code
110
+ xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code), 'currencyID' => currency_code
111
+ xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code), 'currencyID' => currency_code
112
+ xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code
113
+ end
114
+
115
+ xml.tag! 'n2:OrderDescription', options[:description]
116
+ xml.tag! 'n2:InvoiceID', options[:order_id]
117
+
118
+ if options[:items]
119
+ options[:items].each do |item|
120
+ xml.tag! 'n2:PaymentDetailsItem' do
121
+ xml.tag! 'n2:Name', item[:name]
122
+ xml.tag! 'n2:Number', item[:number]
123
+ xml.tag! 'n2:Quantity', item[:quantity]
124
+ if item[:amount]
125
+ xml.tag! 'n2:Amount', localized_amount(item[:amount], currency_code), 'currencyID' => currency_code
126
+ end
127
+ xml.tag! 'n2:Description', item[:description]
128
+ xml.tag! 'n2:ItemURL', item[:url]
129
+ end
130
+ end
131
+ end
132
+
133
+ xml.tag! 'n2:PaymentAction', action
134
+ end
135
+
102
136
  add_address(xml, 'n2:Address', options[:shipping_address] || options[:address])
103
137
  xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0'
104
138
  xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0'
105
139
  xml.tag! 'n2:ReturnURL', options[:return_url]
106
140
  xml.tag! 'n2:CancelURL', options[:cancel_return_url]
107
141
  xml.tag! 'n2:IPAddress', options[:ip] unless options[:ip].blank?
108
- xml.tag! 'n2:OrderDescription', options[:description]
109
142
  xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank?
110
- xml.tag! 'n2:InvoiceID', options[:order_id]
111
143
 
112
144
  if options[:billing_agreement]
113
145
  xml.tag! 'n2:BillingAgreementDetails' do
@@ -119,7 +151,7 @@ module ActiveMerchant #:nodoc:
119
151
 
120
152
  # Customization of the payment page
121
153
  xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank?
122
- xml.tag! 'n2:cpp-image-header', options[:header_image] unless options[:header_image].blank?
154
+ xml.tag! 'n2:cpp-header-image', options[:header_image] unless options[:header_image].blank?
123
155
  xml.tag! 'n2:cpp-header-back-color', options[:header_background_color] unless options[:header_background_color].blank?
124
156
  xml.tag! 'n2:cpp-header-border-color', options[:header_border_color] unless options[:header_border_color].blank?
125
157
  xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank?
@@ -0,0 +1,290 @@
1
+ require 'securerandom'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class QbmsGateway < Gateway
6
+ API_VERSION = '4.0'
7
+
8
+ class_inheritable_accessor :test_url, :live_url
9
+
10
+ self.test_url = "https://webmerchantaccount.ptc.quickbooks.com/j/AppGateway"
11
+ self.live_url = "https://webmerchantaccount.quickbooks.com/j/AppGateway"
12
+
13
+ self.homepage_url = 'http://payments.intuit.com/'
14
+ self.display_name = 'QuickBooks Merchant Services'
15
+ self.default_currency = 'USD'
16
+ self.supported_cardtypes = [ :visa, :master, :discover, :american_express, :diners_club, :jcb ]
17
+ self.supported_countries = [ 'US' ]
18
+
19
+ TYPES = {
20
+ :authorize => 'CustomerCreditCardAuth',
21
+ :capture => 'CustomerCreditCardCapture',
22
+ :purchase => 'CustomerCreditCardCharge',
23
+ :refund => 'CustomerCreditCardTxnVoidOrRefund',
24
+ :void => 'CustomerCreditCardTxnVoid',
25
+ :query => 'MerchantAccountQuery',
26
+ }
27
+
28
+ # Creates a new QbmsGateway
29
+ #
30
+ # The gateway requires that a valid app id, app login, and ticket be passed
31
+ # in the +options+ hash.
32
+ #
33
+ # ==== Options
34
+ #
35
+ # * <tt>:login</tt> -- The App Login (REQUIRED)
36
+ # * <tt>:ticket</tt> -- The Connection Ticket. (REQUIRED)
37
+ # * <tt>:pem</tt> -- The PEM-encoded SSL client key and certificate. (REQUIRED)
38
+ # * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
39
+ # Otherwise, perform transactions against the production server.
40
+ #
41
+ def initialize(options = {})
42
+ requires!(options, :login, :ticket)
43
+ test_mode = options[:test] || false
44
+ @options = options
45
+ super
46
+ end
47
+
48
+ # Performs an authorization, which reserves the funds on the customer's credit card, but does not
49
+ # charge the card.
50
+ #
51
+ # ==== Parameters
52
+ #
53
+ # * <tt>money</tt> -- The amount to be authorized as an Integer value in cents.
54
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
55
+ # * <tt>options</tt> -- A hash of optional parameters.
56
+ #
57
+ def authorize(money, creditcard, options = {})
58
+ commit(:authorize, money, options.merge(:credit_card => creditcard))
59
+ end
60
+
61
+ # Perform a purchase, which is essentially an authorization and capture in a single operation.
62
+ #
63
+ # ==== Parameters
64
+ #
65
+ # * <tt>money</tt> -- The amount to be purchased as an Integer value in cents.
66
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
67
+ # * <tt>options</tt> -- A hash of optional parameters.
68
+ #
69
+ def purchase(money, creditcard, options = {})
70
+ commit(:purchase, money, options.merge(:credit_card => creditcard))
71
+ end
72
+
73
+ # Captures the funds from an authorized transaction.
74
+ #
75
+ # ==== Parameters
76
+ #
77
+ # * <tt>money</tt> -- The amount to be captured as an Integer value in cents.
78
+ # * <tt>authorization</tt> -- The authorization returned from the previous authorize request.
79
+ #
80
+ def capture(money, authorization, options = {})
81
+ commit(:capture, money, options.merge(:transaction_id => authorization))
82
+ end
83
+
84
+ # Void a previous transaction
85
+ #
86
+ # ==== Parameters
87
+ #
88
+ # * <tt>authorization</tt> - The authorization returned from the previous authorize request.
89
+ #
90
+ def void(authorization, options = {})
91
+ commit(:void, nil, options.merge(:transaction_id => authorization))
92
+ end
93
+
94
+ # Credit an account.
95
+ #
96
+ # This transaction is also referred to as a Refund and indicates to the gateway that
97
+ # money should flow from the merchant to the customer.
98
+ #
99
+ # ==== Parameters
100
+ #
101
+ # * <tt>money</tt> -- The amount to be credited to the customer as an Integer value in cents.
102
+ # * <tt>identification</tt> -- The ID of the original transaction against which the credit is being issued.
103
+ # * <tt>options</tt> -- A hash of parameters.
104
+ #
105
+ #
106
+ def credit(money, identification, options = {})
107
+ commit(:refund, money, options.merge(:transaction_id => identification))
108
+ end
109
+
110
+ # Query the merchant account status
111
+ def query
112
+ commit(:query, nil, {})
113
+ end
114
+
115
+ private
116
+
117
+ def hosted?
118
+ @options[:pem]
119
+ end
120
+
121
+ def commit(action, money, parameters)
122
+ url = test? ? self.test_url : self.live_url
123
+
124
+ type = TYPES[action]
125
+ parameters[:trans_request_id] ||= SecureRandom.hex(10)
126
+
127
+ req = build_request(type, money, parameters)
128
+ data = ssl_post(url, req, "Content-Type" => "application/x-qbmsxml")
129
+ response = parse(type, data)
130
+ message = (response[:status_message] || '').strip
131
+
132
+ Response.new(success?(response), message, response,
133
+ :test => test?,
134
+ :authorization => response[:credit_card_trans_id],
135
+ :fraud_review => fraud_review?(response),
136
+ :avs_result => { :code => avs_result(response) },
137
+ :cvv_result => cvv_result(response)
138
+ )
139
+ end
140
+
141
+ def success?(response)
142
+ response[:status_code] == 0
143
+ end
144
+
145
+ def fraud_review?(response)
146
+ [10100, 10101].member? response[:status_code]
147
+ end
148
+
149
+ def parse(type, body)
150
+ xml = REXML::Document.new(body)
151
+
152
+ signon = REXML::XPath.first(xml, "//SignonMsgsRs/#{hosted? ? 'SignonAppCertRs' : 'SignonDesktopRs'}")
153
+ status_code = signon.attributes["statusCode"].to_i
154
+
155
+ if status_code != 0
156
+ return {
157
+ :status_code => status_code,
158
+ :status_message => signon.attributes["statusMessage"],
159
+ }
160
+ end
161
+
162
+ response = REXML::XPath.first(xml, "//QBMSXMLMsgsRs/#{type}Rs")
163
+
164
+ results = {
165
+ :status_code => response.attributes["statusCode"].to_i,
166
+ :status_message => response.attributes["statusMessage"],
167
+ }
168
+
169
+ response.elements.each do |e|
170
+ name = e.name.underscore.to_sym
171
+ value = e.text()
172
+
173
+ if old_value = results[name]
174
+ results[name] = [old_value] if !old_value.kind_of?(Array)
175
+ results[name] << value
176
+ else
177
+ results[name] = value
178
+ end
179
+ end
180
+
181
+ results
182
+ end
183
+
184
+ def build_request(type, money, parameters = {})
185
+ xml = Builder::XmlMarkup.new(:indent => 0)
186
+
187
+ xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
188
+ xml.instruct!(:qbmsxml, :version => API_VERSION)
189
+
190
+ xml.tag!("QBMSXML") do
191
+ xml.tag!("SignonMsgsRq") do
192
+ xml.tag!(hosted? ? "SignonAppCertRq" : "SignonDesktopRq") do
193
+ xml.tag!("ClientDateTime", Time.now.xmlschema)
194
+ xml.tag!("ApplicationLogin", @options[:login])
195
+ xml.tag!("ConnectionTicket", @options[:ticket])
196
+ end
197
+ end
198
+
199
+ xml.tag!("QBMSXMLMsgsRq") do
200
+ xml.tag!("#{type}Rq") do
201
+ method("build_#{type}").call(xml, money, parameters)
202
+ end
203
+ end
204
+ end
205
+
206
+ xml.target!
207
+ end
208
+
209
+ def build_CustomerCreditCardAuth(xml, money, parameters)
210
+ cc = parameters[:credit_card]
211
+ name = "#{cc.first_name} #{cc.last_name}"[0...30]
212
+
213
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
214
+ xml.tag!("CreditCardNumber", cc.number)
215
+ xml.tag!("ExpirationMonth", cc.month)
216
+ xml.tag!("ExpirationYear", cc.year)
217
+ xml.tag!("IsECommerce", "true")
218
+ xml.tag!("Amount", amount(money))
219
+ xml.tag!("NameOnCard", name)
220
+ add_address(xml, parameters)
221
+ xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value?
222
+ end
223
+
224
+ def build_CustomerCreditCardCapture(xml, money, parameters)
225
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
226
+ xml.tag!("CreditCardTransID", parameters[:transaction_id])
227
+ xml.tag!("Amount", amount(money))
228
+ end
229
+
230
+ def build_CustomerCreditCardCharge(xml, money, parameters)
231
+ cc = parameters[:credit_card]
232
+ name = "#{cc.first_name} #{cc.last_name}"[0...30]
233
+
234
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
235
+ xml.tag!("CreditCardNumber", cc.number)
236
+ xml.tag!("ExpirationMonth", cc.month)
237
+ xml.tag!("ExpirationYear", cc.year)
238
+ xml.tag!("IsECommerce", "true")
239
+ xml.tag!("Amount", amount(money))
240
+ xml.tag!("NameOnCard", name)
241
+ add_address(xml, parameters)
242
+ xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value?
243
+ end
244
+
245
+ def build_CustomerCreditCardTxnVoidOrRefund(xml, money, parameters)
246
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
247
+ xml.tag!("CreditCardTransID", parameters[:transaction_id])
248
+ xml.tag!("Amount", amount(money))
249
+ end
250
+
251
+ def build_CustomerCreditCardTxnVoid(xml, money, parameters)
252
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
253
+ xml.tag!("CreditCardTransID", parameters[:transaction_id])
254
+ end
255
+
256
+ # Called reflectively by build_request
257
+ def build_MerchantAccountQuery(xml, money, parameters)
258
+ end
259
+
260
+ def add_address(xml, parameters)
261
+ if address = parameters[:billing_address] || parameters[:address]
262
+ xml.tag!("CreditCardAddress", address[:address1][0...30])
263
+ xml.tag!("CreditCardPostalCode", address[:zip][0...9])
264
+ end
265
+ end
266
+
267
+ def cvv_result(response)
268
+ case response[:card_security_code_match]
269
+ when "Pass" then 'M'
270
+ when "Fail" then 'N'
271
+ when "NotAvailable" then 'P'
272
+ end
273
+ end
274
+
275
+ def avs_result(response)
276
+ case "#{response[:avs_street]}|#{response[:avs_zip]}"
277
+ when "Pass|Pass" then "D"
278
+ when "Pass|Fail" then "A"
279
+ when "Pass|NotAvailable" then "B"
280
+ when "Fail|Pass" then "Z"
281
+ when "Fail|Fail" then "C"
282
+ when "Fail|NotAvailable" then "N"
283
+ when "NotAvailable|Pass" then "P"
284
+ when "NotAvailable|Fail" then "N"
285
+ when "NotAvailable|NotAvailable" then "U"
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end
@@ -14,7 +14,7 @@ module ActiveMerchant #:nodoc:
14
14
  :state => 'custState',
15
15
  :zip => 'custPinCode',
16
16
  :country => 'custCountry',
17
- :phone2 => 'custMobileNo'
17
+ :phone => 'custMobileNo'
18
18
 
19
19
  mapping :shipping_address, :name => 'deliveryName',
20
20
  :city => 'deliveryCity',
@@ -22,7 +22,7 @@ module ActiveMerchant #:nodoc:
22
22
  :state => 'deliveryState',
23
23
  :zip => 'deliveryPinCode',
24
24
  :country => 'deliveryCountry',
25
- :phone2 => 'deliveryMobileNo'
25
+ :phone => 'deliveryMobileNo'
26
26
 
27
27
  mapping :customer, :name => 'custName',
28
28
  :email => 'custEmailId'
@@ -79,16 +79,13 @@ module ActiveMerchant #:nodoc:
79
79
  end
80
80
 
81
81
  def shipping_address(params = {})
82
- add_street_address!(params)
82
+ update_address(:shipping_address, params)
83
83
  super(params.dup)
84
- add_field(mappings[:shipping_address][:name], fields[mappings[:customer][:name]]) if fields[mappings[:shipping_address][:name]].blank?
85
- add_phone_for!(:shipping_address, params)
86
84
  end
87
85
 
88
86
  def billing_address(params = {})
89
- add_street_address!(params)
87
+ update_address(:billing_address, params)
90
88
  super(params.dup)
91
- add_phone_for!(:billing_address, params)
92
89
  end
93
90
 
94
91
  def form_fields
@@ -123,24 +120,27 @@ module ActiveMerchant #:nodoc:
123
120
  end
124
121
  end
125
122
 
126
- def add_street_address!(params)
123
+ def update_address(address_type, params)
127
124
  address = params[:address1]
128
125
  address << " #{params[:address2]}" if params[:address2]
129
- params.merge!(:address1 => address)
126
+ params[:address1] = address
127
+ params[:phone] = normalize_phone_number(params[:phone])
128
+ add_land_line_phone_for(address_type, params)
129
+
130
+ if address_type == :shipping_address && params[:name].blank?
131
+ add_field(mappings[:shipping_address][:name], fields[mappings[:customer][:name]])
132
+ end
130
133
  end
131
-
132
- def add_phone_for!(address_type, params)
134
+
135
+ # Split a single phone number into the country code, area code and local number as best as possible
136
+ def add_land_line_phone_for(address_type, params)
133
137
  address_field = address_type == :billing_address ? 'custPhoneNo' : 'deliveryPhNo'
134
138
 
135
- if params.has_key?(:phone)
136
- country = fields[mappings[address_type][:country]]
137
- phone = params[:phone].to_s
138
- # Remove all non digits
139
- phone.gsub!(/[^\d ]+/, '')
140
-
139
+ if params.has_key?(:phone2)
140
+ phone = normalize_phone_number(params[:phone2])
141
141
  phone_country_code, phone_area_code, phone_number = nil
142
-
143
- if country == 'IN' && phone =~ /(91)? *(\d{3}) *(\d{4,})$/
142
+
143
+ if params[:country] == 'IN' && phone =~ /(91)? *(\d{3}) *(\d{4,})$/
144
144
  phone_country_code, phone_area_code, phone_number = $1, $2, $3
145
145
  else
146
146
  numbers = phone.split(' ')
@@ -154,13 +154,17 @@ module ActiveMerchant #:nodoc:
154
154
  phone_area_code, phone_number = $1, $2
155
155
  end
156
156
  end
157
-
158
- add_field("#{address_field}1", phone_country_code || phone_code_for_country(country) || '91')
157
+
158
+ add_field("#{address_field}1", phone_country_code || phone_code_for_country(params[:country]) || '91')
159
159
  add_field("#{address_field}2", phone_area_code)
160
160
  add_field("#{address_field}3", phone_number)
161
161
  end
162
162
  end
163
163
 
164
+ def normalize_phone_number(phone)
165
+ phone.gsub(/[^\d ]+/, '') if phone
166
+ end
167
+
164
168
  # Special characters are NOT allowed while posting transaction parameters on DirecPay system
165
169
  def remove_special_characters(string)
166
170
  string.gsub(/[~"'&#%]/, '-')