activemerchant 1.21.0 → 1.22.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 (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