activemerchant 1.21.0 → 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +63 -0
  3. data/CONTRIBUTORS +29 -0
  4. data/README.md +195 -0
  5. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +2 -0
  6. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +2 -2
  7. data/lib/active_merchant/billing/gateways/blue_pay.rb +492 -11
  8. data/lib/active_merchant/billing/gateways/braintree_blue.rb +46 -19
  9. data/lib/active_merchant/billing/gateways/certo_direct.rb +1 -1
  10. data/lib/active_merchant/billing/gateways/cyber_source.rb +342 -106
  11. data/lib/active_merchant/billing/gateways/elavon.rb +2 -0
  12. data/lib/active_merchant/billing/gateways/epay.rb +3 -1
  13. data/lib/active_merchant/billing/gateways/itransact.rb +450 -0
  14. data/lib/active_merchant/billing/gateways/migs.rb +259 -0
  15. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
  16. data/lib/active_merchant/billing/gateways/moneris_us.rb +211 -0
  17. data/lib/active_merchant/billing/gateways/ogone.rb +104 -12
  18. data/lib/active_merchant/billing/gateways/orbital.rb +15 -6
  19. data/lib/active_merchant/billing/gateways/paybox_direct.rb +1 -4
  20. data/lib/active_merchant/billing/gateways/payflow.rb +8 -3
  21. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +4 -1
  22. data/lib/active_merchant/billing/gateways/payflow_express.rb +4 -2
  23. data/lib/active_merchant/billing/gateways/payment_express.rb +1 -1
  24. data/lib/active_merchant/billing/gateways/paypal.rb +3 -18
  25. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +287 -1
  26. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +245 -0
  27. data/lib/active_merchant/billing/gateways/paypal_express.rb +14 -66
  28. data/lib/active_merchant/billing/gateways/realex.rb +5 -7
  29. data/lib/active_merchant/billing/gateways/stripe.rb +1 -9
  30. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +2 -2
  31. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -5
  32. data/lib/active_merchant/billing/gateways/viaklix.rb +7 -2
  33. data/lib/active_merchant/billing/gateways/vindicia.rb +359 -0
  34. data/lib/active_merchant/billing/integrations/dotpay.rb +22 -0
  35. data/lib/active_merchant/billing/integrations/dotpay/helper.rb +77 -0
  36. data/lib/active_merchant/billing/integrations/dotpay/notification.rb +86 -0
  37. data/lib/active_merchant/billing/integrations/dotpay/return.rb +11 -0
  38. data/lib/active_merchant/billing/integrations/epay.rb +21 -0
  39. data/lib/active_merchant/billing/integrations/epay/helper.rb +55 -0
  40. data/lib/active_merchant/billing/integrations/epay/notification.rb +110 -0
  41. data/lib/active_merchant/billing/integrations/paypal/notification.rb +2 -1
  42. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +2 -3
  43. data/lib/active_merchant/billing/integrations/robokassa.rb +49 -0
  44. data/lib/active_merchant/billing/integrations/robokassa/common.rb +19 -0
  45. data/lib/active_merchant/billing/integrations/robokassa/helper.rb +50 -0
  46. data/lib/active_merchant/billing/integrations/robokassa/notification.rb +55 -0
  47. data/lib/active_merchant/billing/integrations/robokassa/return.rb +17 -0
  48. data/lib/active_merchant/billing/integrations/two_checkout.rb +25 -3
  49. data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +15 -0
  50. data/lib/active_merchant/billing/integrations/verkkomaksut.rb +20 -0
  51. data/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb +87 -0
  52. data/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb +59 -0
  53. data/lib/active_merchant/version.rb +1 -1
  54. metadata +59 -26
  55. metadata.gz.sig +0 -0
@@ -74,7 +74,7 @@ module ActiveMerchant #:nodoc:
74
74
 
75
75
  def store(creditcard, options = {})
76
76
  commit do
77
- result = Braintree::Customer.create(
77
+ parameters = {
78
78
  :first_name => creditcard.first_name,
79
79
  :last_name => creditcard.last_name,
80
80
  :email => options[:email],
@@ -84,7 +84,8 @@ module ActiveMerchant #:nodoc:
84
84
  :expiration_month => creditcard.month.to_s.rjust(2, "0"),
85
85
  :expiration_year => creditcard.year.to_s
86
86
  }
87
- )
87
+ }
88
+ result = Braintree::Customer.create(merge_credit_card_options(parameters, options))
88
89
  Response.new(result.success?, message_from_result(result),
89
90
  {
90
91
  :braintree_customer => (customer_hash(result.customer) if result.success?),
@@ -99,24 +100,25 @@ module ActiveMerchant #:nodoc:
99
100
  customer_update_result = commit do
100
101
  braintree_credit_card = Braintree::Customer.find(vault_id).credit_cards.detect { |cc| cc.default? }
101
102
  return Response.new(false, 'Braintree::NotFoundError') if braintree_credit_card.nil?
102
- result = Braintree::Customer.update(vault_id,
103
- :first_name => creditcard.first_name,
104
- :last_name => creditcard.last_name,
105
- :email => options[:email]
106
- )
107
- Response.new(result.success?, message_from_result(result),
108
- :braintree_customer => (customer_hash(Braintree::Customer.find(vault_id)) if result.success?)
109
- )
110
- end
111
- return customer_update_result unless customer_update_result.success?
112
- credit_card_update_result = commit do
113
- result = Braintree::CreditCard.update(braintree_credit_card.token,
103
+
104
+ options.merge!(:update_existing_token => braintree_credit_card.token)
105
+ credit_card_params = merge_credit_card_options({
106
+ :credit_card => {
114
107
  :number => creditcard.number,
115
108
  :expiration_month => creditcard.month.to_s.rjust(2, "0"),
116
109
  :expiration_year => creditcard.year.to_s
110
+ }
111
+ }, options)[:credit_card]
112
+
113
+ result = Braintree::Customer.update(vault_id,
114
+ :first_name => creditcard.first_name,
115
+ :last_name => creditcard.last_name,
116
+ :email => options[:email],
117
+ :credit_card => credit_card_params
117
118
  )
118
119
  Response.new(result.success?, message_from_result(result),
119
- :braintree_customer => (customer_hash(Braintree::Customer.find(vault_id)) if result.success?)
120
+ :braintree_customer => (customer_hash(Braintree::Customer.find(vault_id)) if result.success?),
121
+ :customer_vault_id => (result.customer.id if result.success?)
120
122
  )
121
123
  end
122
124
  end
@@ -131,17 +133,38 @@ module ActiveMerchant #:nodoc:
131
133
 
132
134
  private
133
135
 
136
+ def merge_credit_card_options(parameters, options)
137
+ valid_options = {}
138
+ options.each do |key, value|
139
+ valid_options[key] = value if [:update_existing_token, :verify_card, :verification_merchant_account_id].include?(key)
140
+ end
141
+
142
+ parameters[:credit_card] ||= {}
143
+ parameters[:credit_card].merge!(:options => valid_options)
144
+ parameters[:credit_card][:billing_address] = map_address(options[:billing_address]) if options[:billing_address]
145
+ parameters
146
+ end
147
+
134
148
  def map_address(address)
135
149
  return {} if address.nil?
136
- {
150
+ mapped = {
137
151
  :street_address => address[:address1],
138
152
  :extended_address => address[:address2],
139
153
  :company => address[:company],
140
154
  :locality => address[:city],
141
155
  :region => address[:state],
142
156
  :postal_code => address[:zip],
143
- :country_name => address[:country]
144
157
  }
158
+ if(address[:country] || address[:country_code_alpha2])
159
+ mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2])
160
+ elsif address[:country_name]
161
+ mapped[:country_name] = address[:country_name]
162
+ elsif address[:country_code_alpha3]
163
+ mapped[:country_code_alpha3] = address[:country_code_alpha3]
164
+ elsif address[:country_code_numeric]
165
+ mapped[:country_code_numeric] = address[:country_code_numeric]
166
+ end
167
+ mapped
145
168
  end
146
169
 
147
170
  def commit(&block)
@@ -153,6 +176,8 @@ module ActiveMerchant #:nodoc:
153
176
  def message_from_result(result)
154
177
  if result.success?
155
178
  "OK"
179
+ elsif result.errors.size == 0 && result.credit_card_verification
180
+ "Processor declined: #{result.credit_card_verification.processor_response_text} (#{result.credit_card_verification.processor_response_code})"
156
181
  else
157
182
  result.errors.map { |e| "#{e.message} (#{e.code})" }.join(" ")
158
183
  end
@@ -207,7 +232,8 @@ module ActiveMerchant #:nodoc:
207
232
  credit_cards = customer.credit_cards.map do |cc|
208
233
  {
209
234
  "bin" => cc.bin,
210
- "expiration_date" => cc.expiration_date
235
+ "expiration_date" => cc.expiration_date,
236
+ "token" => cc.token
211
237
  }
212
238
  end
213
239
 
@@ -215,7 +241,8 @@ module ActiveMerchant #:nodoc:
215
241
  "email" => customer.email,
216
242
  "first_name" => customer.first_name,
217
243
  "last_name" => customer.last_name,
218
- "credit_cards" => credit_cards
244
+ "credit_cards" => credit_cards,
245
+ "id" => customer.id
219
246
  }
220
247
  end
221
248
 
@@ -7,7 +7,7 @@ module ActiveMerchant #:nodoc:
7
7
  self.supported_countries = [
8
8
  "BE", "BG", "CZ", "DK", "DE", "EE", "IE", "EL", "ES", "FR",
9
9
  "IT", "CY", "LV", "LT", "LU", "HU", "MT", "NL", "AT", "PL",
10
- "PT", "RO", "SI", "SK", "FI", "SE", "UK"
10
+ "PT", "RO", "SI", "SK", "FI", "SE", "GB"
11
11
  ]
12
12
 
13
13
  self.supported_cardtypes = [:visa, :master, :american_express, :discover]
@@ -3,35 +3,37 @@ module ActiveMerchant #:nodoc:
3
3
  # See the remote and mocked unit test files for example usage. Pay special attention to the contents of the options hash.
4
4
  #
5
5
  # Initial setup instructions can be found in http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf
6
- #
7
- # Debugging
6
+ #
7
+ # Debugging
8
8
  # If you experience an issue with this gateway be sure to examine the transaction information from a general transaction search inside the CyberSource Business
9
- # Center for the full error messages including field names.
9
+ # Center for the full error messages including field names.
10
10
  #
11
11
  # Important Notes
12
- # * AVS and CVV only work against the production server. You will always get back X for AVS and no response for CVV against the test server.
13
- # * Nexus is the list of states or provinces where you have a physical presence. Nexus is used to calculate tax. Leave blank to tax everyone.
14
- # * If you want to calculate VAT for overseas customers you must supply a registration number in the options hash as vat_reg_number.
12
+ # * AVS and CVV only work against the production server. You will always get back X for AVS and no response for CVV against the test server.
13
+ # * Nexus is the list of states or provinces where you have a physical presence. Nexus is used to calculate tax. Leave blank to tax everyone.
14
+ # * If you want to calculate VAT for overseas customers you must supply a registration number in the options hash as vat_reg_number.
15
15
  # * productCode is a value in the line_items hash that is used to tell CyberSource what kind of item you are selling. It is used when calculating tax/VAT.
16
16
  # * All transactions use dollar values.
17
17
  class CyberSourceGateway < Gateway
18
18
  TEST_URL = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor'
19
19
  LIVE_URL = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor'
20
-
20
+
21
+ XSD_VERSION = "1.69"
22
+
21
23
  # visa, master, american_express, discover
22
24
  self.supported_cardtypes = [:visa, :master, :american_express, :discover]
23
25
  self.supported_countries = ['US']
24
26
  self.default_currency = 'USD'
25
27
  self.homepage_url = 'http://www.cybersource.com'
26
28
  self.display_name = 'CyberSource'
27
-
29
+
28
30
  # map credit card to the CyberSource expected representation
29
31
  @@credit_card_codes = {
30
32
  :visa => '001',
31
33
  :master => '002',
32
34
  :american_express => '003',
33
35
  :discover => '004'
34
- }
36
+ }
35
37
 
36
38
  # map response codes to something humans can read
37
39
  @@response_codes = {
@@ -43,17 +45,17 @@ module ActiveMerchant #:nodoc:
43
45
  :r152 => "The request was received, but a service timed out",
44
46
  :r200 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check",
45
47
  :r201 => "The issuing bank has questions about the request",
46
- :r202 => "Expired card",
47
- :r203 => "General decline of the card",
48
- :r204 => "Insufficient funds in the account",
49
- :r205 => "Stolen or lost card",
50
- :r207 => "Issuing bank unavailable",
51
- :r208 => "Inactive card or card not authorized for card-not-present transactions",
52
- :r209 => "American Express Card Identifiction Digits (CID) did not match",
53
- :r210 => "The card has reached the credit limit",
54
- :r211 => "Invalid card verification number",
55
- :r221 => "The customer matched an entry on the processor's negative file",
56
- :r230 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check",
48
+ :r202 => "Expired card",
49
+ :r203 => "General decline of the card",
50
+ :r204 => "Insufficient funds in the account",
51
+ :r205 => "Stolen or lost card",
52
+ :r207 => "Issuing bank unavailable",
53
+ :r208 => "Inactive card or card not authorized for card-not-present transactions",
54
+ :r209 => "American Express Card Identifiction Digits (CID) did not match",
55
+ :r210 => "The card has reached the credit limit",
56
+ :r211 => "Invalid card verification number",
57
+ :r221 => "The customer matched an entry on the processor's negative file",
58
+ :r230 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check",
57
59
  :r231 => "Invalid account number",
58
60
  :r232 => "The card type is not accepted by the payment processor",
59
61
  :r233 => "General decline by the processor",
@@ -72,44 +74,44 @@ module ActiveMerchant #:nodoc:
72
74
  :r247 => "You requested a credit for a capture that was previously voided",
73
75
  :r250 => "The request was received, but a time-out occurred with the payment processor",
74
76
  :r254 => "Your CyberSource account is prohibited from processing stand-alone refunds",
75
- :r255 => "Your CyberSource account is not configured to process the service in the country you specified"
77
+ :r255 => "Your CyberSource account is not configured to process the service in the country you specified"
76
78
  }
77
79
 
78
80
  # These are the options that can be used when creating a new CyberSource Gateway object.
79
- #
80
- # :login => your username
81
81
  #
82
- # :password => the transaction key you generated in the Business Center
82
+ # :login => your username
83
+ #
84
+ # :password => the transaction key you generated in the Business Center
83
85
  #
84
86
  # :test => true sets the gateway to test mode
85
87
  #
86
- # :vat_reg_number => your VAT registration number
88
+ # :vat_reg_number => your VAT registration number
87
89
  #
88
90
  # :nexus => "WI CA QC" sets the states/provinces where you have a physical presense for tax purposes
89
91
  #
90
- # :ignore_avs => true don't want to use AVS so continue processing even if AVS would have failed
92
+ # :ignore_avs => true don't want to use AVS so continue processing even if AVS would have failed
91
93
  #
92
- # :ignore_cvv => true don't want to use CVV so continue processing even if CVV would have failed
94
+ # :ignore_cvv => true don't want to use CVV so continue processing even if CVV would have failed
93
95
  def initialize(options = {})
94
96
  requires!(options, :login, :password)
95
97
  @options = options
96
98
  super
97
- end
99
+ end
98
100
 
99
101
  # Should run against the test servers or not?
100
102
  def test?
101
103
  @options[:test] || Base.gateway_mode == :test
102
104
  end
103
-
104
- # Request an authorization for an amount from CyberSource
105
+
106
+ # Request an authorization for an amount from CyberSource
105
107
  #
106
- # You must supply an :order_id in the options hash
108
+ # You must supply an :order_id in the options hash
107
109
  def authorize(money, creditcard, options = {})
108
110
  requires!(options, :order_id, :email)
109
111
  setup_address_hash(options)
110
112
  commit(build_auth_request(money, creditcard, options), options )
111
113
  end
112
-
114
+
113
115
  def auth_reversal(money, identification, options = {})
114
116
  commit(build_auth_reversal_request(money, identification, options), options)
115
117
  end
@@ -121,13 +123,18 @@ module ActiveMerchant #:nodoc:
121
123
  end
122
124
 
123
125
  # Purchase is an auth followed by a capture
124
- # You must supply an order_id in the options hash
125
- def purchase(money, creditcard, options = {})
126
+ # You must supply an order_id in the options hash
127
+ def purchase(money, payment_source, options = {})
126
128
  requires!(options, :order_id, :email)
127
129
  setup_address_hash(options)
128
- commit(build_purchase_request(money, creditcard, options), options)
130
+ if payment_source.is_a?(String)
131
+ requires!(options, [:type, :credit_card, :check])
132
+ commit(build_subscription_purchase_request(money, payment_source, options), options)
133
+ else
134
+ commit(build_purchase_request(money, payment_source, options), options)
135
+ end
129
136
  end
130
-
137
+
131
138
  def void(identification, options = {})
132
139
  commit(build_void_request(identification, options), options)
133
140
  end
@@ -135,17 +142,80 @@ module ActiveMerchant #:nodoc:
135
142
  def refund(money, identification, options = {})
136
143
  commit(build_credit_request(money, identification, options), options)
137
144
  end
138
-
145
+
139
146
  def credit(money, identification, options = {})
140
147
  deprecated CREDIT_DEPRECATION_MESSAGE
141
148
  refund(money, identification, options)
142
149
  end
143
150
 
151
+ # Creates or updates a cybersource customer profile, aka a subscription with type "on-demand"
152
+ # to charge the card while creating a profile, pass options[:setup_fee] => money
153
+ def store(credit_card_or_reference, options = {})
154
+
155
+ requires!(options, :order_id)
156
+ setup_address_hash(options)
157
+
158
+ if credit_card_or_reference.respond_to?(:number)
159
+ # create subscription
160
+ requires!(options, :billing_address, :email)
161
+ requires!(options[:billing_address], :first_name, :last_name)
162
+
163
+ # set subscription options for storing the credit card
164
+ options[:subscription] ||={}
165
+ options[:subscription].merge!(:frequency => "on-demand", :amount => 0, :auto_renew => false)
166
+
167
+ setup_address_hash(options)
168
+ request = build_create_subscription_request(credit_card_or_reference, options)
169
+ else
170
+ # update subscription
171
+ request = build_update_subscription_request(credit_card_or_reference, options)
172
+ end
173
+
174
+ commit(request, options)
175
+ end
176
+
177
+ # retrieves a customer subscription/profile
178
+ def retrieve(reference, options = {})
179
+ requires!(options, :order_id)
180
+ commit(build_retrieve_subscription_request(reference, options), options)
181
+ end
182
+
183
+ # removes a customer subscription/profile
184
+ def unstore(reference, options = {})
185
+ requires!(options, :order_id)
186
+ commit(build_delete_subscription_request(reference, options), options)
187
+ end
188
+
189
+ # Creates or updates a Cybersource recurring payment profile/subscription
190
+ def recurring(money, credit_card_or_reference, options = {})
191
+ requires!(options, :order_id, :subscription)
192
+ requires!(options[:subscription], [:frequency, "on-demand", "weekly", "bi-weekly", "semi-monthly", "quarterly", "quad-weekly", "semi-annually", "annually"])
193
+
194
+ options[:subscription].merge!(:amount => money)
195
+
196
+ setup_address_hash(options)
197
+
198
+ if credit_card_or_reference.respond_to?(:number)
199
+ # create subscription
200
+ requires!(options, :billing_address, :email)
201
+ requires!(options[:billing_address], :first_name, :last_name)
202
+
203
+ setup_address_hash(options)
204
+ request = build_create_subscription_request(credit_card_or_reference, options)
205
+ else
206
+ # update subscription
207
+ request = build_update_subscription_request(credit_card_or_reference, options)
208
+ end
209
+
210
+ commit(request, options)
211
+ end
212
+
213
+
144
214
  # CyberSource requires that you provide line item information for tax calculations
145
215
  # If you do not have prices for each item or want to simplify the situation then pass in one fake line item that costs the subtotal of the order
146
216
  #
147
- # The line_item hash goes in the options hash and should look like
148
- #
217
+ # The line_item hash goes in the options hash and should look like
218
+ #
149
219
  # :line_items => [
150
220
  # {
151
221
  # :declared_value => '1',
@@ -165,22 +235,23 @@ module ActiveMerchant #:nodoc:
165
235
  #
166
236
  # This functionality is only supported by this particular gateway may
167
237
  # be changed at any time
168
- def calculate_tax(creditcard, options)
238
+ def calculate_tax(options)
169
239
  requires!(options, :line_items)
240
+
170
241
  setup_address_hash(options)
171
- commit(build_tax_calculation_request(creditcard, options), options)
242
+ commit(build_tax_calculation_request(options), options)
172
243
  end
173
-
174
- private
175
- # Create all address hash key value pairs so that we still function if we were only provided with one or two of them
244
+
245
+ private
246
+ # Create all address hash key value pairs so that we still function if we were only provided with one or two of them
176
247
  def setup_address_hash(options)
177
248
  options[:billing_address] = options[:billing_address] || options[:address] || {}
178
249
  options[:shipping_address] = options[:shipping_address] || {}
179
250
  end
180
-
251
+
181
252
  def build_auth_request(money, creditcard, options)
182
253
  xml = Builder::XmlMarkup.new :indent => 2
183
- add_address(xml, creditcard, options[:billing_address], options)
254
+ add_address(xml, options[:billing_address], options)
184
255
  add_purchase_data(xml, money, true, options)
185
256
  add_creditcard(xml, creditcard)
186
257
  add_auth_service(xml)
@@ -188,17 +259,17 @@ module ActiveMerchant #:nodoc:
188
259
  xml.target!
189
260
  end
190
261
 
191
- def build_tax_calculation_request(creditcard, options)
262
+ def build_tax_calculation_request(options)
192
263
  xml = Builder::XmlMarkup.new :indent => 2
193
- add_address(xml, creditcard, options[:billing_address], options, false)
194
- add_address(xml, creditcard, options[:shipping_address], options, true)
264
+ add_address(xml, options[:billing_address], options, false)
265
+ add_address(xml, options[:shipping_address], options, true) unless options[:shipping_address].empty?
195
266
  add_line_item_data(xml, options)
196
267
  add_purchase_data(xml, 0, false, options)
197
- add_tax_service(xml)
268
+ add_tax_service(xml, options)
198
269
  add_business_rules_data(xml)
199
270
  xml.target!
200
271
  end
201
-
272
+
202
273
  def build_capture_request(money, authorization, options)
203
274
  order_id, request_id, request_token = authorization.split(";")
204
275
  options[:order_id] = order_id
@@ -208,22 +279,22 @@ module ActiveMerchant #:nodoc:
208
279
  add_capture_service(xml, request_id, request_token)
209
280
  add_business_rules_data(xml)
210
281
  xml.target!
211
- end
282
+ end
212
283
 
213
- def build_purchase_request(money, creditcard, options)
284
+ def build_purchase_request(money, payment_source, options)
214
285
  xml = Builder::XmlMarkup.new :indent => 2
215
- add_address(xml, creditcard, options[:billing_address], options)
286
+ add_address(xml, options[:billing_address], options)
216
287
  add_purchase_data(xml, money, true, options)
217
- add_creditcard(xml, creditcard)
218
- add_purchase_service(xml, options)
288
+ add_payment_source(xml, payment_source)
289
+ add_purchase_service(xml, payment_source, options)
219
290
  add_business_rules_data(xml)
220
291
  xml.target!
221
292
  end
222
-
293
+
223
294
  def build_void_request(identification, options)
224
295
  order_id, request_id, request_token = identification.split(";")
225
296
  options[:order_id] = order_id
226
-
297
+
227
298
  xml = Builder::XmlMarkup.new :indent => 2
228
299
  add_void_service(xml, request_id, request_token)
229
300
  xml.target!
@@ -241,11 +312,84 @@ module ActiveMerchant #:nodoc:
241
312
  def build_credit_request(money, identification, options)
242
313
  order_id, request_id, request_token = identification.split(";")
243
314
  options[:order_id] = order_id
244
-
315
+
245
316
  xml = Builder::XmlMarkup.new :indent => 2
246
317
  add_purchase_data(xml, money, true, options)
247
318
  add_credit_service(xml, request_id, request_token)
248
-
319
+
320
+ xml.target!
321
+ end
322
+
323
+ def build_create_subscription_request(payment_source, options)
324
+ xml = Builder::XmlMarkup.new :indent => 2
325
+ add_address(xml, options[:billing_address], options)
326
+ add_purchase_data(xml, options[:setup_fee], true, options)
327
+
328
+
329
+ case determine_funding_source(payment_source)
330
+ when :credit_card then add_creditcard(xml, payment_source)
331
+ when :check then add_check(xml, payment_source)
332
+ else raise ArgumentError, "Unsupported funding source provided"
333
+ end
334
+
335
+ add_subscription(xml, options, payment_source)
336
+ add_subscription_create_service(xml, options)
337
+ add_business_rules_data(xml)
338
+ xml.target!
339
+ end
340
+
341
+ def build_update_subscription_request(identification, options)
342
+ reference_code, subscription_id, request_token = identification.split(";")
343
+ options[:subscription] ||= {}
344
+ options[:subscription][:subscription_id] = subscription_id
345
+
346
+ xml = Builder::XmlMarkup.new :indent => 2
347
+ add_address(xml, options[:billing_address], options) unless options[:billing_address].blank?
348
+ add_purchase_data(xml, options[:setup_fee], true, options) unless options[:setup_fee].blank?
349
+ add_creditcard(xml, options[:credit_card]) if options[:credit_card]
350
+ add_subscription(xml, options)
351
+ add_subscription_update_service(xml, options)
352
+ add_business_rules_data(xml)
353
+ xml.target!
354
+ end
355
+
356
+ def build_retrieve_subscription_request(identification, options)
357
+ reference_code, subscription_id, request_token = identification.split(";")
358
+ options[:subscription] ||= {}
359
+ options[:subscription][:subscription_id] = subscription_id
360
+
361
+ xml = Builder::XmlMarkup.new :indent => 2
362
+ add_subscription(xml, options)
363
+ add_subscription_retrieve_service(xml, options)
364
+ xml.target!
365
+ end
366
+
367
+ def build_delete_subscription_request(identification, options)
368
+ reference_code, subscription_id, request_token = identification.split(";")
369
+ options[:subscription] ||= {}
370
+ options[:subscription][:subscription_id] = subscription_id
371
+
372
+ xml = Builder::XmlMarkup.new :indent => 2
373
+ add_subscription(xml, options)
374
+ add_subscription_delete_service(xml, options)
375
+ xml.target!
376
+ end
377
+
378
+ def build_subscription_purchase_request(money, identification, options)
379
+ reference_code, subscription_id, request_token = identification.split(";")
380
+ options[:subscription] ||= {}
381
+ options[:subscription][:subscription_id] = subscription_id
382
+
383
+ xml = Builder::XmlMarkup.new :indent => 2
384
+ add_purchase_data(xml, money, true, options)
385
+ add_subscription(xml, options)
386
+
387
+ case options[:type]
388
+ when :credit_card then add_cc_purchase_service(xml, options)
389
+ when :check then add_check_service(xml)
390
+ end
391
+
392
+ add_business_rules_data(xml)
249
393
  xml.target!
250
394
  end
251
395
 
@@ -253,13 +397,13 @@ module ActiveMerchant #:nodoc:
253
397
  xml.tag! 'businessRules' do
254
398
  xml.tag!('ignoreAVSResult', 'true') if @options[:ignore_avs]
255
399
  xml.tag!('ignoreCVResult', 'true') if @options[:ignore_cvv]
256
- end
400
+ end
257
401
  end
258
-
402
+
259
403
  def add_line_item_data(xml, options)
260
404
  options[:line_items].each_with_index do |value, index|
261
405
  xml.tag! 'item', {'id' => index} do
262
- xml.tag! 'unitPrice', amount(value[:declared_value])
406
+ xml.tag! 'unitPrice', amount(value[:declared_value])
263
407
  xml.tag! 'quantity', value[:quantity]
264
408
  xml.tag! 'productCode', value[:code] || 'shipping_only'
265
409
  xml.tag! 'productName', value[:description]
@@ -267,37 +411,51 @@ module ActiveMerchant #:nodoc:
267
411
  end
268
412
  end
269
413
  end
270
-
414
+
271
415
  def add_merchant_data(xml, options)
272
416
  xml.tag! 'merchantID', @options[:login]
273
417
  xml.tag! 'merchantReferenceCode', options[:order_id]
274
418
  xml.tag! 'clientLibrary' ,'Ruby Active Merchant'
275
- xml.tag! 'clientLibraryVersion', '1.0'
276
- xml.tag! 'clientEnvironment' , 'Linux'
419
+ xml.tag! 'clientLibraryVersion', VERSION
420
+ xml.tag! 'clientEnvironment' , RUBY_PLATFORM
421
+ end
422
+
423
+ def add_payment_source(xml, source, options={})
424
+ case determine_funding_source(source)
425
+ #when :subscription then add_customer_vault_id(params, source)
426
+ when :credit_card then add_creditcard(xml, source)
427
+ when :check then add_check(xml, source)
428
+ end
277
429
  end
278
430
 
279
- def add_purchase_data(xml, money = 0, include_grand_total = false, options={})
431
+ def add_purchase_data(xml, money, include_grand_total = false, options={})
432
+ money ||=0
280
433
  xml.tag! 'purchaseTotals' do
281
434
  xml.tag! 'currency', options[:currency] || currency(money)
282
- xml.tag!('grandTotalAmount', amount(money)) if include_grand_total
435
+ xml.tag!('grandTotalAmount', amount(money)) if include_grand_total
283
436
  end
284
437
  end
285
438
 
286
- def add_address(xml, creditcard, address, options, shipTo = false)
439
+ def add_address(xml, address, options, shipTo = false)
287
440
  xml.tag! shipTo ? 'shipTo' : 'billTo' do
288
- xml.tag! 'firstName', creditcard.first_name
289
- xml.tag! 'lastName', creditcard.last_name
290
- xml.tag! 'street1', address[:address1]
291
- xml.tag! 'street2', address[:address2]
292
- xml.tag! 'city', address[:city]
293
- xml.tag! 'state', address[:state]
294
- xml.tag! 'postalCode', address[:zip]
295
- xml.tag! 'country', address[:country]
296
- xml.tag! 'email', options[:email]
297
- end
298
- end
299
-
300
- def add_creditcard(xml, creditcard)
441
+ xml.tag! 'firstName', address[:first_name]
442
+ xml.tag! 'lastName', address[:last_name]
443
+ xml.tag! 'street1', address[:address1]
444
+ xml.tag! 'street2', address[:address2]
445
+ xml.tag! 'city', address[:city]
446
+ xml.tag! 'state', address[:state]
447
+ xml.tag! 'postalCode', address[:zip]
448
+ xml.tag! 'country', address[:country]
449
+ xml.tag! 'company', address[:company] unless address[:company].blank?
450
+ xml.tag! 'companyTaxID', address[:companyTaxID] unless address[:company_tax_id].blank?
451
+ xml.tag! 'phoneNumber', address[:phone_number] unless address[:phone_number].blank?
452
+ xml.tag! 'email', options[:email] unless options[:email].blank?
453
+ xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank?
454
+ xml.tag! 'driversLicenseState', options[:drivers_license_state] unless options[:drivers_license_state].blank?
455
+ end
456
+ end
457
+
458
+ def add_creditcard(xml, creditcard)
301
459
  xml.tag! 'card' do
302
460
  xml.tag! 'accountNumber', creditcard.number
303
461
  xml.tag! 'expirationMonth', format(creditcard.month, :two_digits)
@@ -307,15 +465,35 @@ module ActiveMerchant #:nodoc:
307
465
  end
308
466
  end
309
467
 
310
- def add_tax_service(xml)
468
+ def add_check(xml, check)
469
+ #convert check object account type into cybs account type code
470
+ if check.account_type == "checking"
471
+ accountType = check.account_holder_type == "business" ? 'X' : 'C'
472
+ else
473
+ accountType = 'S'
474
+ end
475
+
476
+ xml.tag! 'check' do
477
+ xml.tag! 'accountNumber', check.account_number
478
+ xml.tag! 'accountType', accountType
479
+ xml.tag! 'bankTransitNumber', check.routing_number
480
+ xml.tag! 'checkNumber', check.number if check.number
481
+ end
482
+ end
483
+
484
+ def add_check_service(xml)
485
+ xml.tag! 'ecDebitService', {'run' => 'true'}
486
+ end
487
+
488
+ def add_tax_service(xml, options)
311
489
  xml.tag! 'taxService', {'run' => 'true'} do
312
- xml.tag!('nexus', @options[:nexus]) unless @options[:nexus].blank?
313
- xml.tag!('sellerRegistration', @options[:vat_reg_number]) unless @options[:vat_reg_number].blank?
490
+ xml.tag!('nexus', options[:nexus]) unless options[:nexus].blank?
491
+ xml.tag!('sellerRegistration', @ptions[:vat_reg_number]) unless options[:vat_reg_number].blank?
314
492
  end
315
493
  end
316
494
 
317
495
  def add_auth_service(xml)
318
- xml.tag! 'ccAuthService', {'run' => 'true'}
496
+ xml.tag! 'ccAuthService', {'run' => 'true'}
319
497
  end
320
498
 
321
499
  def add_capture_service(xml, request_id, request_token)
@@ -325,11 +503,18 @@ module ActiveMerchant #:nodoc:
325
503
  end
326
504
  end
327
505
 
328
- def add_purchase_service(xml, options)
506
+ def add_purchase_service(xml, source, options)
507
+ case determine_funding_source(source)
508
+ when :credit_card then add_cc_purchase_service(xml, options)
509
+ when :check then add_check_service(xml)
510
+ end
511
+ end
512
+
513
+ def add_cc_purchase_service(xml, options)
329
514
  xml.tag! 'ccAuthService', {'run' => 'true'}
330
515
  xml.tag! 'ccCaptureService', {'run' => 'true'}
331
516
  end
332
-
517
+
333
518
  def add_void_service(xml, request_id, request_token)
334
519
  xml.tag! 'voidService', {'run' => 'true'} do
335
520
  xml.tag! 'voidRequestID', request_id
@@ -351,7 +536,47 @@ module ActiveMerchant #:nodoc:
351
536
  end
352
537
  end
353
538
 
354
-
539
+
540
+ def add_subscription_create_service(xml, options)
541
+ add_cc_purchase_service(xml, options) if options[:setup_fee]
542
+ xml.tag! 'paySubscriptionCreateService', {'run' => 'true'}
543
+ end
544
+
545
+ def add_subscription_update_service(xml, options)
546
+ add_cc_purchase_service(xml, options) if options[:setup_fee]
547
+ xml.tag! 'paySubscriptionUpdateService', {'run' => 'true'}
548
+ end
549
+
550
+ def add_subscription_retrieve_service(xml, options)
551
+ xml.tag! 'paySubscriptionRetrieveService', {'run' => 'true'}
552
+ end
553
+
554
+ def add_subscription_delete_service(xml, options)
555
+ xml.tag! 'paySubscriptionDeleteService', {'run' => 'true'}
556
+ end
557
+
558
+ def add_subscription(xml, options, payment_source=nil)
559
+ if payment_source
560
+ xml.tag! 'subscription' do
561
+ xml.tag! 'paymentMethod', determine_funding_source(payment_source).to_s.gsub(/_/, " ")
562
+ end
563
+ end
564
+
565
+ xml.tag! 'recurringSubscriptionInfo' do
566
+ xml.tag! 'subscriptionID', options[:subscription][:subscription_id]
567
+ xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status]
568
+ xml.tag! 'amount', options[:subscription][:amount] if options[:subscription][:amount]
569
+ xml.tag! 'numberOfPayments', options[:subscription][:occurrences] if options[:subscription][:occurrences]
570
+ xml.tag! 'automaticRenew', options[:subscription][:auto_renew] if options[:subscription][:auto_renew]
571
+ xml.tag! 'frequency', options[:subscription][:frequency] if options[:subscription][:frequency]
572
+ xml.tag! 'startDate', options[:subscription][:start_date].strftime("%Y%m%d") if options[:subscription][:start_date]
573
+ xml.tag! 'endDate', options[:subscription][:end_date].strftime("%Y%m%d") if options[:subscription][:end_date]
574
+ xml.tag! 'approvalRequired', options[:subscription][:approval_required] || false
575
+ xml.tag! 'event', options[:subscription][:event] if options[:subscription][:event]
576
+ xml.tag! 'billPayment', options[:subscription][:bill_payment] if options[:subscription][:bill_payment]
577
+ end
578
+ end
579
+
355
580
  # Where we actually build the full SOAP request using builder
356
581
  def build_request(body, options)
357
582
  xml = Builder::XmlMarkup.new :indent => 2
@@ -366,31 +591,32 @@ module ActiveMerchant #:nodoc:
366
591
  end
367
592
  end
368
593
  xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do
369
- xml.tag! 'requestMessage', {'xmlns' => 'urn:schemas-cybersource-com:transaction-data-1.32'} do
594
+ xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{XSD_VERSION}"} do
370
595
  add_merchant_data(xml, options)
371
596
  xml << body
372
597
  end
373
598
  end
374
599
  end
375
- xml.target!
600
+ xml.target!
376
601
  end
377
-
602
+
378
603
  # Contact CyberSource, make the SOAP request, and parse the reply into a Response object
379
604
  def commit(request, options)
380
- response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, build_request(request, options)))
381
-
605
+ request = build_request(request, options)
606
+ post_response = ssl_post(test? ? TEST_URL : LIVE_URL, request)
607
+ response = parse(post_response)
382
608
  success = response[:decision] == "ACCEPT"
383
- message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
609
+ message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
384
610
  authorization = success ? [ options[:order_id], response[:requestID], response[:requestToken] ].compact.join(";") : nil
385
-
386
- Response.new(success, message, response,
387
- :test => test?,
611
+
612
+ Response.new(success, message, response,
613
+ :test => test?,
388
614
  :authorization => authorization,
389
615
  :avs_result => { :code => response[:avsCode] },
390
616
  :cvv_result => response[:cvCode]
391
617
  )
392
618
  end
393
-
619
+
394
620
  # Parse the SOAP response
395
621
  # Technique inspired by the Paypal Gateway
396
622
  def parse(xml)
@@ -398,19 +624,19 @@ module ActiveMerchant #:nodoc:
398
624
  xml = REXML::Document.new(xml)
399
625
  if root = REXML::XPath.first(xml, "//c:replyMessage")
400
626
  root.elements.to_a.each do |node|
401
- case node.name
627
+ case node.name
402
628
  when 'c:reasonCode'
403
629
  reply[:message] = reply(node.text)
404
630
  else
405
631
  parse_element(reply, node)
406
632
  end
407
633
  end
408
- elsif root = REXML::XPath.first(xml, "//soap:Fault")
634
+ elsif root = REXML::XPath.first(xml, "//soap:Fault")
409
635
  parse_element(reply, root)
410
636
  reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}"
411
637
  end
412
638
  return reply
413
- end
639
+ end
414
640
 
415
641
  def parse_element(reply, node)
416
642
  if node.has_elements?
@@ -419,12 +645,22 @@ module ActiveMerchant #:nodoc:
419
645
  if node.parent.name =~ /item/
420
646
  parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '')
421
647
  reply[(parent + '_' + node.name).to_sym] = node.text
422
- else
648
+ else
423
649
  reply[node.name.to_sym] = node.text
424
650
  end
425
651
  end
426
652
  return reply
427
653
  end
428
- end
429
- end
430
- end
654
+
655
+ def determine_funding_source(source)
656
+ case
657
+ when source.is_a?(String) then :subscription
658
+ when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card
659
+ when card_brand(source) == 'check' then :check
660
+ else raise ArgumentError, "Unsupported funding source provided"
661
+ end
662
+ end
663
+
664
+ end
665
+ end
666
+ end