solidus_gateway 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +23 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.md +26 -0
  7. data/README.md +38 -0
  8. data/Rakefile +21 -0
  9. data/app/models/spree/billing_integration/skrill/quick_checkout.rb +48 -0
  10. data/app/models/spree/gateway/authorize_net.rb +31 -0
  11. data/app/models/spree/gateway/authorize_net_cim.rb +201 -0
  12. data/app/models/spree/gateway/balanced_gateway.rb +65 -0
  13. data/app/models/spree/gateway/banwire.rb +15 -0
  14. data/app/models/spree/gateway/beanstream.rb +193 -0
  15. data/app/models/spree/gateway/braintree_gateway.rb +184 -0
  16. data/app/models/spree/gateway/card_save.rb +10 -0
  17. data/app/models/spree/gateway/data_cash.rb +10 -0
  18. data/app/models/spree/gateway/eway.rb +20 -0
  19. data/app/models/spree/gateway/linkpoint.rb +28 -0
  20. data/app/models/spree/gateway/maxipago.rb +14 -0
  21. data/app/models/spree/gateway/migs.rb +11 -0
  22. data/app/models/spree/gateway/moneris.rb +10 -0
  23. data/app/models/spree/gateway/pay_junction.rb +16 -0
  24. data/app/models/spree/gateway/pay_pal_gateway.rb +12 -0
  25. data/app/models/spree/gateway/payflow_pro.rb +18 -0
  26. data/app/models/spree/gateway/paymill.rb +12 -0
  27. data/app/models/spree/gateway/pin_gateway.rb +60 -0
  28. data/app/models/spree/gateway/sage_pay.rb +11 -0
  29. data/app/models/spree/gateway/secure_pay_au.rb +10 -0
  30. data/app/models/spree/gateway/spreedly_core_gateway.rb +10 -0
  31. data/app/models/spree/gateway/stripe_gateway.rb +126 -0
  32. data/app/models/spree/gateway/usa_epay.rb +9 -0
  33. data/app/models/spree/gateway/worldpay.rb +91 -0
  34. data/app/models/spree/skrill_transaction.rb +19 -0
  35. data/bin/rails +7 -0
  36. data/circle.yml +6 -0
  37. data/config/locales/bg.yml +11 -0
  38. data/config/locales/de.yml +11 -0
  39. data/config/locales/en.yml +30 -0
  40. data/config/locales/sv.yml +11 -0
  41. data/config/routes.rb +12 -0
  42. data/db/migrate/20111118164631_create_skrill_transactions.rb +14 -0
  43. data/db/migrate/20121017004102_update_braintree_payment_method_type.rb +9 -0
  44. data/db/migrate/20130213222555_update_stripe_payment_method_type.rb +9 -0
  45. data/db/migrate/20130415222802_update_balanced_payment_method_type.rb +9 -0
  46. data/db/migrate/20131008221012_update_paypal_payment_method_type.rb +9 -0
  47. data/db/migrate/20131112133401_migrate_stripe_preferences.rb +8 -0
  48. data/lib/active_merchant/billing/skrill.rb +18 -0
  49. data/lib/assets/javascripts/spree/frontend/solidus_gateway.js +1 -0
  50. data/lib/assets/stylesheets/spree/frontend/solidus_gateway.css +3 -0
  51. data/lib/controllers/frontend/spree/checkout_controller_decorator.rb +51 -0
  52. data/lib/controllers/frontend/spree/skrill_status_controller.rb +39 -0
  53. data/lib/generators/solidus_gateway/install/install_generator.rb +28 -0
  54. data/lib/solidus_gateway.rb +4 -0
  55. data/lib/spree_gateway/engine.rb +67 -0
  56. data/lib/views/backend/spree/admin/log_entries/_braintree.html.erb +31 -0
  57. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +28 -0
  58. data/lib/views/backend/spree/admin/payments/source_forms/_quickcheckout.html.erb +8 -0
  59. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +1 -0
  60. data/lib/views/backend/spree/admin/payments/source_views/_quickcheckout.html.erb +39 -0
  61. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +1 -0
  62. data/lib/views/frontend/spree/checkout/payment/_quickcheckout.html.erb +26 -0
  63. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +80 -0
  64. data/solidus_gateway.gemspec +33 -0
  65. data/spec/factories/skrill_factory.rb +6 -0
  66. data/spec/features/stripe_checkout_spec.rb +78 -0
  67. data/spec/lib/active_merchant/billing_skrill_spec.rb +18 -0
  68. data/spec/models/billing_integration/skrill_quick_checkout_spec.rb +11 -0
  69. data/spec/models/gateway/authorize_net_cim_spec.rb +29 -0
  70. data/spec/models/gateway/authorize_net_spec.rb +23 -0
  71. data/spec/models/gateway/balanced_gateway_spec.rb +17 -0
  72. data/spec/models/gateway/banwire_spec.rb +11 -0
  73. data/spec/models/gateway/beanstream_spec.rb +17 -0
  74. data/spec/models/gateway/braintree_gateway_spec.rb +437 -0
  75. data/spec/models/gateway/card_save_spec.rb +11 -0
  76. data/spec/models/gateway/data_cache_spec.rb +11 -0
  77. data/spec/models/gateway/eway_spec.rb +29 -0
  78. data/spec/models/gateway/linkpoint_spec.rb +62 -0
  79. data/spec/models/gateway/maxipago_spec.rb +17 -0
  80. data/spec/models/gateway/moneris_spec.rb +11 -0
  81. data/spec/models/gateway/pay_junction_spec.rb +23 -0
  82. data/spec/models/gateway/pay_pal_spec.rb +11 -0
  83. data/spec/models/gateway/payflow_pro_spec.rb +23 -0
  84. data/spec/models/gateway/paymill_spec.rb +11 -0
  85. data/spec/models/gateway/pin_gateway_spec.rb +55 -0
  86. data/spec/models/gateway/sage_pay_spec.rb +11 -0
  87. data/spec/models/gateway/secure_pay_au_spec.rb +11 -0
  88. data/spec/models/gateway/stripe_gateway_spec.rb +200 -0
  89. data/spec/models/gateway/usa_epay_spec.rb +49 -0
  90. data/spec/models/gateway/worldpay_spec.rb +11 -0
  91. data/spec/models/skrill_transaction_spec.rb +9 -0
  92. data/spec/spec_helper.rb +50 -0
  93. metadata +317 -0
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ class Gateway::Banwire < Gateway
3
+ preference :login, :string
4
+
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::BanwireGateway
8
+ end
9
+
10
+ def purchase(money, creditcard, gateway_options)
11
+ gateway_options[:description] = "Spree Order"
12
+ provider.purchase(money, creditcard, gateway_options)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,193 @@
1
+ module Spree
2
+ class Gateway::Beanstream < Gateway
3
+ preference :login, :string
4
+ preference :user, :string
5
+ preference :password, :string
6
+ preference :secure_profile_api_key, :string
7
+
8
+ def provider_class
9
+ ActiveMerchant::Billing::BeanstreamGateway
10
+ end
11
+
12
+ def payment_profiles_supported?
13
+ true
14
+ end
15
+
16
+ def create_profile(payment)
17
+ creditcard = payment.source
18
+ if creditcard.gateway_customer_profile_id.nil?
19
+ options = options_for_create_customer_profile(creditcard, {})
20
+ verify_creditcard_name!(creditcard)
21
+ result = provider.store(creditcard, options)
22
+ if result.success?
23
+ creditcard.update_attributes(:gateway_customer_profile_id => result.params['customerCode'], :gateway_payment_profile_id => result.params['customer_vault_id'])
24
+ else
25
+ payment.send(:gateway_error, result)
26
+ end
27
+ end
28
+ end
29
+
30
+ def capture(transaction, creditcard, gateway_options)
31
+ beanstream_gateway.capture((transaction.amount*100).round, transaction.response_code, gateway_options)
32
+ end
33
+
34
+ def void(transaction_response, creditcard, gateway_options)
35
+ beanstream_gateway.void(transaction_response, gateway_options)
36
+ end
37
+
38
+ def credit(amount, creditcard, response_code, gateway_options = {})
39
+ amount = (amount * -1) if amount < 0
40
+ beanstream_gateway.credit(amount, response_code, gateway_options)
41
+ end
42
+
43
+ private
44
+ def beanstream_gateway
45
+ ActiveMerchant::Billing::Base.gateway_mode = preferred_server.to_sym
46
+ gateway_options = options
47
+ ActiveMerchant::Billing::BeanstreamGateway.new(gateway_options)
48
+ end
49
+
50
+ def verify_creditcard_name!(creditcard)
51
+ bill_address = creditcard.payments.first.order.bill_address
52
+ creditcard.first_name = bill_address.firstname unless creditcard.first_name?
53
+ creditcard.last_name = bill_address.lastname unless creditcard.last_name?
54
+ end
55
+
56
+ def options_for_create_customer_profile(creditcard, gateway_options)
57
+ order = creditcard.payments.first.order
58
+ address = order.bill_address
59
+ { :email=>order.email,
60
+ :billing_address=>
61
+ { :name=>address.full_name,
62
+ :phone=>address.phone,
63
+ :address1=>address.address1,
64
+ :address2=>address.address2,
65
+ :city=>address.city,
66
+ :state=>address.state_name || address.state.abbr,
67
+ :country=>address.country.iso,
68
+ :zip=>address.zipcode
69
+ }
70
+ }.merge(gateway_options)
71
+ end
72
+
73
+ SECURE_PROFILE_URL = 'https://www.beanstream.com/scripts/payment_profile.asp'
74
+ SP_SERVICE_VERSION = '1.1'
75
+ PROFILE_OPERATIONS = { :new => 'N', :modify => 'M' }
76
+
77
+ ActiveMerchant::Billing::BeanstreamGateway.class_eval do
78
+
79
+ def store(credit_card, options = {})
80
+ post = {}
81
+ add_address(post, options)
82
+ add_credit_card(post, credit_card)
83
+ add_secure_profile_variables(post,options)
84
+ commit(post, true)
85
+ end
86
+
87
+ #can't actually delete a secure profile with the supplicaed API. This function sets the status of the profile to closed (C).
88
+ #Closed profiles will have to removed manually.
89
+ def delete(vault_id)
90
+ update(vault_id, false, { :status => 'C' })
91
+ end
92
+
93
+ #alias_method :unstore, :delete
94
+
95
+ # Update the values (such as CC expiration) stored at
96
+ # the gateway. The CC number must be supplied in the
97
+ # CreditCard object.
98
+ def update(vault_id, credit_card, options = {})
99
+ post = {}
100
+ add_address(post, options)
101
+ add_credit_card(post, credit_card)
102
+ options.merge!({ :vault_id => vault_id, :operation => secure_profile_action(:modify) })
103
+ add_secure_profile_variables(post,options)
104
+ commit(post, true)
105
+ end
106
+
107
+ # CORE #
108
+
109
+ def secure_profile_action(type)
110
+ PROFILE_OPERATIONS[type] || PROFILE_OPERATIONS[:new]
111
+ end
112
+
113
+ def add_credit_card(post, credit_card)
114
+ if credit_card
115
+ if credit_card.has_payment_profile?
116
+ post[:customerCode] = credit_card.gateway_customer_profile_id
117
+ else
118
+ post[:trnCardOwner] = credit_card.name
119
+ post[:trnCardNumber] = credit_card.number
120
+ post[:trnExpMonth] = format(credit_card.month, :two_digits)
121
+ post[:trnExpYear] = format(credit_card.year, :two_digits)
122
+ post[:trnCardCvd] = credit_card.verification_value
123
+ end
124
+ end
125
+ end
126
+
127
+ def add_secure_profile_variables(post, options = {})
128
+ post[:serviceVersion] = SP_SERVICE_VERSION
129
+ post[:responseFormat] = 'QS'
130
+ post[:cardValidation] = (options[:cardValidation].to_i == 1) || '0'
131
+
132
+ post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new)
133
+ post[:customerCode] = options[:billing_id] || options[:vault_id] || false
134
+ post[:status] = options[:status]
135
+ end
136
+
137
+ def commit(params, use_profile_api = false)
138
+ post(post_data(params,use_profile_api),use_profile_api)
139
+ end
140
+
141
+ def post(data, use_profile_api)
142
+ response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : ActiveMerchant::Billing::BeanstreamGateway::URL), data))
143
+ if response[:responseCode].eql?("17") # Found matching card
144
+ response[:responseCode] = "1"
145
+ response[:customerCode] = response[:matchedCustomerCode]
146
+ end
147
+ response[:customer_vault_id] = response[:customerCode] if response[:customerCode]
148
+ build_response(success?(response), message_from(response), response,
149
+ :test => test? || response[:authCode] == 'TEST',
150
+ :authorization => authorization_from(response),
151
+ :cvv_result => ActiveMerchant::Billing::BeanstreamGateway::CVD_CODES[response[:cvdId]],
152
+ :avs_result => { :code => (ActiveMerchant::Billing::BeanstreamGateway::AVS_CODES.include? response[:avsId]) ? ActiveMerchant::Billing::BeanstreamGateway::AVS_CODES[response[:avsId]] : response[:avsId] }
153
+ )
154
+ end
155
+
156
+ def message_from(response)
157
+ response[:messageText] || response[:responseMessage]
158
+ end
159
+
160
+ def success?(response)
161
+ response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1'
162
+ end
163
+
164
+ def add_source(post, source)
165
+ if source.is_a?(String) or source.is_a?(Integer)
166
+ post[:customerCode] = source
167
+ else
168
+ if source.respond_to?(:type) && source.type.to_s == 'check'
169
+ add_check(post, source)
170
+ else
171
+ add_credit_card(post, source)
172
+ end
173
+ end
174
+ end
175
+
176
+ def post_data(params, use_profile_api)
177
+ params[:requestType] = 'BACKEND'
178
+ if use_profile_api
179
+ params[:merchantId] = @options[:login]
180
+ params[:passCode] = @options[:secure_profile_api_key]
181
+ else
182
+ params[:username] = @options[:user] if @options[:user]
183
+ params[:password] = @options[:password] if @options[:password]
184
+ params[:merchant_id] = @options[:login]
185
+ end
186
+ params[:vbvEnabled] = '0'
187
+ params[:scEnabled] = '0'
188
+
189
+ params.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,184 @@
1
+ module Spree
2
+ class Gateway::BraintreeGateway < Gateway
3
+ preference :environment, :string
4
+ preference :merchant_id, :string
5
+ preference :merchant_account_id, :string
6
+ preference :public_key, :string
7
+ preference :private_key, :string
8
+ preference :client_side_encryption_key, :text
9
+
10
+ CARD_TYPE_MAPPING = {
11
+ 'American Express' => 'american_express',
12
+ 'Diners Club' => 'diners_club',
13
+ 'Discover' => 'discover',
14
+ 'JCB' => 'jcb',
15
+ 'Laser' => 'laser',
16
+ 'Maestro' => 'maestro',
17
+ 'MasterCard' => 'master',
18
+ 'Solo' => 'solo',
19
+ 'Switch' => 'switch',
20
+ 'Visa' => 'visa'
21
+ }
22
+
23
+ def provider
24
+ provider_instance = super
25
+ Braintree::Configuration.custom_user_agent = "Spree #{Spree.version}"
26
+ Braintree::Configuration.environment = preferred_environment.to_sym
27
+ Braintree::Configuration.merchant_id = preferred_merchant_id
28
+ Braintree::Configuration.public_key = preferred_public_key
29
+ Braintree::Configuration.private_key = preferred_private_key
30
+
31
+ provider_instance
32
+ end
33
+
34
+ def provider_class
35
+ ActiveMerchant::Billing::BraintreeBlueGateway
36
+ end
37
+
38
+ def authorize(money, creditcard, options = {})
39
+ adjust_options_for_braintree(creditcard, options)
40
+
41
+ if creditcard.gateway_payment_profile_id
42
+ payment_method = creditcard.gateway_payment_profile_id
43
+ options[:payment_method_token] = true
44
+ else
45
+ payment_method = creditcard.gateway_customer_profile_id || creditcard
46
+ end
47
+
48
+ provider.authorize(money, payment_method, options)
49
+ end
50
+
51
+ def capture(amount, authorization_code, ignored_options = {})
52
+ provider.capture(amount, authorization_code)
53
+ end
54
+
55
+ def create_profile(payment)
56
+ if payment.source.gateway_customer_profile_id.nil?
57
+ response = provider.store(payment.source, options_for_payment(payment))
58
+
59
+ if response.success?
60
+ payment.source.update_attributes!(:gateway_customer_profile_id => response.params['customer_vault_id'])
61
+ cc = response.params['braintree_customer'].fetch('credit_cards',[]).first
62
+ update_card_number(payment.source, cc) if cc
63
+ else
64
+ payment.send(:gateway_error, response.message)
65
+ end
66
+ end
67
+ end
68
+
69
+ def update_card_number(source, cc)
70
+ last_4 = cc['last_4']
71
+ source.last_digits = last_4 if last_4
72
+ source.gateway_payment_profile_id = cc['token']
73
+ source.cc_type = CARD_TYPE_MAPPING[cc['card_type']] if cc['card_type']
74
+ source.save!
75
+ end
76
+
77
+ def credit(*args)
78
+ if args.size == 4
79
+ # enables ability to refund instead of credit
80
+ args.slice!(1,1)
81
+ credit_without_payment_profiles(*args)
82
+ elsif args.size == 3
83
+ credit_without_payment_profiles(*args)
84
+ else
85
+ raise ArgumentError, "Expected 3 or 4 arguments, received #{args.size}"
86
+ end
87
+ end
88
+
89
+ # Braintree now disables credits by default, see https://www.braintreepayments.com/docs/ruby/transactions/credit
90
+ def credit_with_payment_profiles(amount, payment, response_code, option)
91
+ provider.credit(amount, payment)
92
+ end
93
+
94
+ def credit_without_payment_profiles(amount, response_code, options)
95
+ provider # braintree provider needs to be called here to properly configure braintree gem.
96
+ transaction = ::Braintree::Transaction.find(response_code)
97
+ if BigDecimal.new(amount.to_s) == (transaction.amount * 100)
98
+ provider.refund(response_code)
99
+ elsif BigDecimal.new(amount.to_s) < (transaction.amount * 100) # support partial refunds
100
+ provider.refund(amount, response_code)
101
+ else
102
+ raise NotImplementedError
103
+ end
104
+ end
105
+
106
+ def payment_profiles_supported?
107
+ true
108
+ end
109
+
110
+ def purchase(money, creditcard, options = {})
111
+ authorize(money, creditcard, options.merge(:submit_for_settlement => true))
112
+ end
113
+
114
+ def void(response_code, *ignored_options)
115
+ provider.void(response_code)
116
+ end
117
+
118
+ def options
119
+ h = super
120
+ # We need to add merchant_account_id only if present when creating BraintreeBlueGateway
121
+ # Remove it since it is always part of the preferences hash.
122
+ if h[:merchant_account_id].blank?
123
+ h.delete(:merchant_account_id)
124
+ end
125
+ h
126
+ end
127
+
128
+ def cancel(response_code)
129
+ provider
130
+ transaction = ::Braintree::Transaction.find(response_code)
131
+ # From: https://www.braintreepayments.com/docs/ruby/transactions/refund
132
+ # "A transaction can be refunded if its status is settled or settling.
133
+ # If the transaction has not yet begun settlement, it should be voided instead of refunded.
134
+ if transaction.status == Braintree::Transaction::Status::SubmittedForSettlement
135
+ provider.void(response_code)
136
+ else
137
+ provider.refund(response_code)
138
+ end
139
+ end
140
+
141
+ protected
142
+
143
+ def adjust_billing_address(creditcard, options)
144
+ if creditcard.gateway_customer_profile_id
145
+ options.delete(:billing_address)
146
+ end
147
+ end
148
+
149
+ def adjust_options_for_braintree(creditcard, options)
150
+ adjust_billing_address(creditcard, options)
151
+ end
152
+
153
+ def options_for_payment(p)
154
+ o = Hash.new
155
+ o[:email] = p.order.email
156
+
157
+ if p.source.gateway_customer_profile_id.present?
158
+ o[:customer] = p.source.gateway_customer_profile_id
159
+ end
160
+
161
+ if p.order.bill_address
162
+ bill_addr = p.order.bill_address
163
+
164
+ o[:first_name] = bill_addr.firstname
165
+ o[:last_name] = bill_addr.lastname
166
+
167
+ o[:billing_address] = {
168
+ address1: bill_addr.address1,
169
+ address2: bill_addr.address2,
170
+ company: bill_addr.company,
171
+ city: bill_addr.city,
172
+ state: bill_addr.state ? bill_addr.state.name : bill_addr.state_name,
173
+ country_code_alpha3: bill_addr.country.iso3,
174
+ zip: bill_addr.zipcode
175
+ }
176
+ end
177
+
178
+ o[:verify_card] = "true"
179
+
180
+ return o
181
+ end
182
+
183
+ end
184
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ class Gateway::CardSave < Gateway
3
+ preference :login, :string
4
+ preference :password, :string
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::CardSaveGateway
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ class Gateway::DataCash < Gateway
3
+ preference :login, :string
4
+ preference :password, :string
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::DataCashGateway
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Spree
2
+ class Gateway::Eway < Gateway
3
+ preference :login, :string
4
+
5
+ # Note: EWay supports purchase method only (no authorize method).
6
+ def auto_capture?
7
+ true
8
+ end
9
+
10
+ def provider_class
11
+ ActiveMerchant::Billing::EwayGateway
12
+ end
13
+
14
+ def options_with_test_preference
15
+ options_without_test_preference.merge(:test => self.preferred_test_mode)
16
+ end
17
+
18
+ alias_method_chain :options, :test_preference
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ class Gateway::Linkpoint < Gateway
3
+ preference :login, :string
4
+ preference :pem, :text
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::LinkpointGateway
8
+ end
9
+
10
+ [:authorize, :purchase, :capture, :void, :credit].each do |method|
11
+ define_method(method) do |*args|
12
+ options = add_discount_to_subtotal(args.extract_options!)
13
+ provider.public_send(method, *args << options)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # Linkpoint ignores the discount, but it will return an error if the
20
+ # chargetotal is different from the sum of the subtotal, tax and
21
+ # shipping totals.
22
+ def add_discount_to_subtotal(options)
23
+ subtotal = options.fetch(:subtotal)
24
+ discount = options.fetch(:discount)
25
+ options.merge(subtotal: subtotal + discount, discount: 0)
26
+ end
27
+ end
28
+ end