spree_gateway 3.7.2 → 3.9.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +31 -38
  3. data/Appraisals +4 -10
  4. data/app/models/spree/check.rb +41 -0
  5. data/app/models/spree/checkout_controller_decorator.rb +19 -0
  6. data/app/models/spree/gateway/stripe_ach_gateway.rb +60 -0
  7. data/app/models/spree/gateway/stripe_elements_gateway.rb +50 -0
  8. data/app/models/spree/gateway/stripe_gateway.rb +1 -0
  9. data/app/models/spree/order_decorator.rb +28 -0
  10. data/app/models/spree/payment_decorator.rb +34 -0
  11. data/app/views/spree/checkout/_payment_confirm.html.erb +34 -0
  12. data/config/initializers/spree_permitted_attributes.rb +5 -0
  13. data/config/locales/en.yml +23 -0
  14. data/config/routes.rb +12 -0
  15. data/db/migrate/20200317135551_add_spree_check_payment_source.rb +22 -0
  16. data/db/migrate/20200422114908_add_intent_key_to_payment.rb +5 -0
  17. data/gemfiles/{spree_3_2.gemfile → spree_4_1.gemfile} +1 -1
  18. data/gemfiles/{spree_3_5.gemfile → spree_4_2.gemfile} +1 -1
  19. data/lib/active_merchant/billing/stripe_gateway_decorator.rb +13 -0
  20. data/lib/controllers/spree/api/v2/storefront/intents_controller.rb +27 -0
  21. data/lib/spree_gateway.rb +1 -0
  22. data/lib/spree_gateway/engine.rb +1 -0
  23. data/lib/spree_gateway/version.rb +1 -1
  24. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_ach.html.erb +86 -0
  25. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_apple_pay.html.erb +0 -0
  26. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_elements.html.erb +79 -0
  27. data/lib/views/backend/spree/admin/payments/source_views/_stripe_ach.html.erb +21 -0
  28. data/lib/views/backend/spree/admin/payments/source_views/_stripe_apple_pay.html.erb +1 -0
  29. data/lib/views/backend/spree/admin/payments/source_views/_stripe_elements.html.erb +1 -0
  30. data/lib/views/frontend/spree/checkout/payment/_stripe_ach.html.erb +81 -0
  31. data/lib/views/frontend/spree/checkout/payment/_stripe_ach_verify.html.erb +16 -0
  32. data/lib/views/frontend/spree/checkout/payment/_stripe_apple_pay.html.erb +10 -1
  33. data/lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb +2 -1
  34. data/spec/factories/check_factory.rb +10 -0
  35. data/spec/features/admin/stripe_elements_payment_spec.rb +109 -0
  36. data/spec/features/stripe_checkout_spec.rb +3 -0
  37. data/spec/features/stripe_elements_3ds_checkout_spec.rb +105 -0
  38. data/spec/models/gateway/stripe_ach_gateway_spec.rb +186 -0
  39. data/spec/models/gateway/stripe_gateway_spec.rb +1 -0
  40. data/spec/spec_helper.rb +7 -64
  41. data/spec/support/within_stripe_3ds_popup.rb +10 -0
  42. data/spree_gateway.gemspec +4 -22
  43. metadata +43 -269
  44. data/gemfiles/spree_3_7.gemfile +0 -9
  45. data/gemfiles/spree_4_0.gemfile +0 -8
@@ -103,7 +103,8 @@
103
103
  }
104
104
  });
105
105
  };
106
-
106
+
107
107
  </script>
108
108
 
109
109
  <%= render 'spree/checkout/payment/stripe_additional_info' %>
110
+
@@ -0,0 +1,10 @@
1
+ FactoryBot.define do
2
+ factory :check, class: Spree::Check do
3
+ account_holder_name { 'John Doe' }
4
+ account_holder_type { 'Individual' }
5
+ account_type { 'checking' }
6
+ routing_number { '110000000' }
7
+ account_number { '000123456789' }
8
+ association(:payment_method, factory: :credit_card_payment_method)
9
+ end
10
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Admin Panel Stripe elements payment', type: :feature, :js => true do
4
+ stub_authorization!
5
+
6
+ let!(:country) { create(:country, :states_required => true) }
7
+ let!(:state) { create(:state, :country => country) }
8
+ let!(:shipping_method) { create(:shipping_method) }
9
+ let!(:stock_location) { create(:stock_location) }
10
+ let!(:mug) { create(:product, :name => 'RoR Mug') }
11
+ let!(:zone) { create(:zone) }
12
+ let!(:stripe_elements_payment_method) do
13
+ Spree::Gateway::StripeElementsGateway.create!(
14
+ :name => 'Stripe Element',
15
+ :preferred_secret_key => 'sk_test_VCZnDv3GLU15TRvn8i2EsaAN',
16
+ :preferred_publishable_key => 'pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg'
17
+ )
18
+ end
19
+
20
+ let!(:order) { OrderWalkthrough.up_to(:payment) }
21
+ before { visit spree.new_admin_order_payment_path(order.number) }
22
+
23
+ it 'can process a valid payment' do
24
+ fill_in_stripe_payment
25
+ wait_for { !page.has_current_path?(spree.admin_order_payments_path(order.number)) }
26
+
27
+ expect(page.body).to have_content('Payment has been successfully created!')
28
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
29
+ end
30
+
31
+ if Spree.version.to_f >= 4.1
32
+ it 'shows an error with an invalid card name' do
33
+ fill_in_stripe_payment(true)
34
+
35
+ expect(page).to have_content("Credit card Name can't be blank")
36
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
37
+ end
38
+ else
39
+ it 'can proces valid payment with invalid card name' do
40
+ fill_in_stripe_payment(true)
41
+ wait_for { !page.has_current_path?(spree.admin_order_payments_path(order.number)) }
42
+
43
+ expect(page.body).to have_content('Payment has been successfully created!')
44
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
45
+ end
46
+ end
47
+
48
+ it 'shows an error with an invalid card number' do
49
+ fill_in_stripe_payment(false, true)
50
+
51
+ expect(page).to have_content('The card number is not a valid credit card number.')
52
+ expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
53
+ end
54
+
55
+ it 'shows an error with an invalid card code' do
56
+ fill_in_stripe_payment(false, false, true)
57
+
58
+ expect(page).to have_content("Your card's security code is invalid.")
59
+ expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
60
+ end
61
+
62
+ it 'shows an error with an invalid card expiration' do
63
+ fill_in_stripe_payment(false, false, false, true)
64
+
65
+ if Spree.version.to_f >= 4.1
66
+ expect(page).to have_content('Credit card Month is not a number')
67
+ expect(page).to have_content('Credit card Year is not a number')
68
+ expect(page).to have_current_path spree.admin_order_payments_path(order.number)
69
+ else
70
+ expect(page).to have_content("Your card's expiration year is invalid.")
71
+ expect(page).to have_current_path spree.new_admin_order_payment_path(order.number)
72
+ end
73
+ end
74
+
75
+ def fill_in_stripe_payment(invalid_name = false, invalid_number = false, invalid_code = false, invalid_expiration = false)
76
+ fill_in 'Name *', with: invalid_name ? '' : 'Stripe Elements Gateway Payment'
77
+ fill_in_card_number(invalid_number)
78
+ fill_in_cvc(invalid_code)
79
+ fill_in_card_expiration(invalid_expiration)
80
+
81
+ click_button 'Update'
82
+ end
83
+
84
+ def fill_in_card_number(invalid_number)
85
+ number = invalid_number ? '123' : '4242 4242 4242 4242'
86
+ fill_in_field('Card Number *', '#card_number1', number)
87
+ end
88
+
89
+ def fill_in_card_expiration(invalid_expiration)
90
+ valid_expiry = Spree.version.to_f >= 4.2 ? "01/#{Time.current.year + 1}" : "01 / #{Time.current.year + 1}"
91
+ invalid_expiry = Spree.version.to_f >= 4.2 ? '01/' : '01 / '
92
+
93
+ card_expiry = invalid_expiration ? invalid_expiry : valid_expiry
94
+ fill_in_field('Expiration *', '#card_expiry1', card_expiry)
95
+ end
96
+
97
+ def fill_in_cvc(invalid_code)
98
+ value = invalid_code ? '1' : '123'
99
+ label = Spree.version.to_f >= 4.2 ? 'Card Varification Code (CVC) *' : 'Card Code *'
100
+
101
+ fill_in label, with: value
102
+ end
103
+
104
+ def fill_in_field(field_name, field_id, number)
105
+ until page.find(field_id).value == number
106
+ fill_in field_name, with: number
107
+ end
108
+ end
109
+ end
@@ -91,7 +91,10 @@ describe "Stripe checkout", type: :feature do
91
91
  expect(page).to have_css('.has-error #card_code.error')
92
92
  end
93
93
 
94
+ # this scenario will not occur on Spree 4.2 due to swapping jquery.payment to cleave
95
+ # see https://github.com/spree/spree/pull/10363
94
96
  it "shows an error with invalid expiry month field", :js => true do
97
+ skip if Spree.version.to_f >= 4.2
95
98
  fill_in 'card_number', with: '4242 4242 4242 4242'
96
99
  fill_in 'card_expiry', :with => "00 / #{Time.now.year + 1}"
97
100
  fill_in 'card_code', with: '123'
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Stripe Elements 3ds checkout', type: :feature, js: true do
6
+ let!(:product) { create(:product, name: 'RoR Mug') }
7
+ let!(:stripe_payment_method) do
8
+ Spree::Gateway::StripeElementsGateway.create!(
9
+ name: 'Stripe',
10
+ preferred_secret_key: 'sk_test_VCZnDv3GLU15TRvn8i2EsaAN',
11
+ preferred_publishable_key: 'pk_test_Cuf0PNtiAkkMpTVC2gwYDMIg',
12
+ preferred_intents: preferred_intents
13
+ )
14
+ end
15
+
16
+ before do
17
+ user = create(:user)
18
+ order = OrderWalkthrough.up_to(:confirm)
19
+ expect(order).to receive(:confirmation_required?).and_return(true).at_least(:once)
20
+
21
+ order.reload
22
+ order.user = user
23
+ payment = order.payments.first
24
+ payment.source = create(:credit_card, number: card_number)
25
+ payment.save!
26
+
27
+ allow_any_instance_of(Spree::CheckoutController).to receive_messages(current_order: order)
28
+ allow_any_instance_of(Spree::CheckoutController).to receive_messages(try_spree_current_user: user)
29
+ allow_any_instance_of(Spree::CheckoutController).to receive_messages(skip_state_validation?: true)
30
+ allow_any_instance_of(Spree::OrdersController).to receive_messages(try_spree_current_user: user)
31
+
32
+ add_to_cart(product)
33
+ click_link 'checkout'
34
+ click_button 'Place Order'
35
+ end
36
+
37
+ describe 'when intents are disabled' do
38
+ let(:preferred_intents) { false }
39
+
40
+ context 'and credit card does not require 3ds authentication' do
41
+ let(:card_number) { '4242424242424242' }
42
+
43
+ it 'should place order without 3ds authentication' do
44
+ expect(page).to have_content('Order placed successfully')
45
+ order = Spree::Order.complete.last
46
+ expect(page.current_url).to include("/orders/#{order.number}")
47
+ expect(page).to have_content(order.number)
48
+ end
49
+ end
50
+
51
+ context 'and credit card does require 3ds authentication' do
52
+ let(:card_number) { '4000000000003220' }
53
+
54
+ it 'should not place the order' do
55
+ expect(page).to have_content('Your card was declined. This transaction requires authentication.')
56
+ expect(Spree::Order.complete.last).to be_nil
57
+ end
58
+ end
59
+ end
60
+
61
+ describe 'when intents are enabled' do
62
+ let(:preferred_intents) { true }
63
+
64
+ context 'and credit card does not require 3ds authentication' do
65
+ let(:card_number) { '4242424242424242' }
66
+
67
+ it 'should successfully place order without 3ds authentication' do
68
+ expect(page).to have_content('Order placed successfully')
69
+ order = Spree::Order.complete.last
70
+ expect(page.current_url).to include("/orders/#{order.number}")
71
+ expect(page).to have_content(order.number)
72
+ end
73
+ end
74
+
75
+ context 'when credit card does require 3ds authentication' do
76
+ let(:card_number) { '4000000000003220' }
77
+
78
+ context 'and authentication is successful' do
79
+ it 'should place order after 3ds authentication' do
80
+ within_stripe_3ds_popup do
81
+ click_button('Complete')
82
+ end
83
+
84
+ expect(page).to have_content('Order placed successfully')
85
+ order = Spree::Order.complete.last
86
+ expect(page.current_url).to include("/orders/#{order.number}")
87
+ expect(page).to have_content(order.number)
88
+ end
89
+ end
90
+
91
+ context 'and authentication is unsuccessful' do
92
+ it 'should not place order after 3ds authentication' do
93
+ within_stripe_3ds_popup do
94
+ click_button('Fail')
95
+ end
96
+
97
+ expect(page).to_not have_content('Order placed successfully')
98
+ expect(page).to have_content('We are unable to authenticate your payment method.')
99
+ expect(page).to have_content('Please choose a different payment method and try again.')
100
+ expect(Spree::Order.complete.last).to be_nil
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Gateway::StripeAchGateway do
4
+ let(:secret_key) { 'key' }
5
+ let(:email) { 'customer@example.com' }
6
+ let(:source) { Spree::Check.new }
7
+ let(:payment) {
8
+ double('Spree::Payment',
9
+ source: source,
10
+ order: double('Spree::Order',
11
+ email: email,
12
+ bill_address: bill_address,
13
+ user: double('Spree::User',
14
+ email: email)
15
+ )
16
+ )
17
+ }
18
+ let(:provider) do
19
+ double('provider').tap do |p|
20
+ p.stub(:purchase)
21
+ p.stub(:authorize)
22
+ p.stub(:capture)
23
+ p.stub(:verify)
24
+ end
25
+ end
26
+
27
+ before do
28
+ subject.preferences = { secret_key: secret_key }
29
+ subject.stub(:options_for_purchase_or_auth).and_return(['money','check','opts'])
30
+ subject.stub(:provider).and_return provider
31
+ end
32
+
33
+ describe '#create_profile' do
34
+ before do
35
+ payment.source.stub(:update!)
36
+ end
37
+
38
+ context 'with an order that has a bill address' do
39
+ let(:bill_address) {
40
+ double('Spree::Address',
41
+ address1: '123 Happy Road',
42
+ address2: 'Apt 303',
43
+ city: 'Suzarac',
44
+ zipcode: '95671',
45
+ state: double('Spree::State', name: 'Oregon'),
46
+ country: double('Spree::Country', name: 'United States')
47
+ )
48
+ }
49
+
50
+ it 'stores the bill address with the provider' do
51
+ subject.provider.should_receive(:store).with(payment.source, {
52
+ email: email,
53
+ login: secret_key,
54
+
55
+ address: {
56
+ address1: '123 Happy Road',
57
+ address2: 'Apt 303',
58
+ city: 'Suzarac',
59
+ zip: '95671',
60
+ state: 'Oregon',
61
+ country: 'United States'
62
+ }
63
+ }).and_return double.as_null_object
64
+
65
+ subject.create_profile payment
66
+ end
67
+ end
68
+
69
+ context 'with an order that does not have a bill address' do
70
+ let(:bill_address) { nil }
71
+
72
+ it 'does not store a bill address with the provider' do
73
+ subject.provider.should_receive(:store).with(payment.source, {
74
+ email: email,
75
+ login: secret_key
76
+ }).and_return double.as_null_object
77
+
78
+ subject.create_profile payment
79
+ end
80
+
81
+ end
82
+
83
+ context 'with a check represents payment_profile' do
84
+ let(:source) { Spree::Check.new(gateway_payment_profile_id: 'tok_profileid') }
85
+ let(:bill_address) { nil }
86
+
87
+ it 'stores the profile_id as a check' do
88
+ subject.provider.should_receive(:store).with(source.gateway_payment_profile_id, anything).and_return double.as_null_object
89
+
90
+ subject.create_profile payment
91
+ end
92
+ end
93
+ end
94
+
95
+ context 'purchasing' do
96
+ after do
97
+ subject.purchase(19.99, 'check', {})
98
+ end
99
+
100
+ it 'send the payment to the provider' do
101
+ provider.should_receive(:purchase).with('money', 'check', 'opts')
102
+ end
103
+ end
104
+
105
+ context 'authorizing' do
106
+ after do
107
+ subject.authorize(19.99, 'check', {})
108
+ end
109
+
110
+ it 'send the authorization to the provider' do
111
+ provider.should_receive(:authorize).with('money', 'check', 'opts')
112
+ end
113
+ end
114
+
115
+ context 'verifying' do
116
+ after do
117
+ subject.verify(source, {})
118
+ end
119
+
120
+ it 'send the verify to the provider' do
121
+ provider.should_receive(:verify).with(source, anything)
122
+ end
123
+ end
124
+
125
+ context 'capturing' do
126
+
127
+ after do
128
+ subject.capture(1234, 'response_code', {})
129
+ end
130
+
131
+ it 'convert the amount to cents' do
132
+ provider.should_receive(:capture).with(1234, anything, anything)
133
+ end
134
+
135
+ it 'use the response code as the authorization' do
136
+ provider.should_receive(:capture).with(anything, 'response_code', anything)
137
+ end
138
+ end
139
+
140
+ context 'capture with payment class' do
141
+ let(:gateway) do
142
+ gateway = described_class.new(active: true)
143
+ gateway.set_preference :secret_key, secret_key
144
+ gateway.stub(:options_for_purchase_or_auth).and_return(['money', 'check', 'opts'])
145
+ gateway.stub(:provider).and_return provider
146
+ gateway.stub source_required: true
147
+ gateway
148
+ end
149
+
150
+ let(:order) { Spree::Order.create }
151
+
152
+ let(:check) do
153
+ # mock_model(Spree::Check, :gateway_customer_profile_id => 'cus_abcde',
154
+ # :imported => false)
155
+ create :check, gateway_customer_profile_id: 'cus_abcde', imported: false
156
+ end
157
+
158
+ let(:payment) do
159
+ payment = Spree::Payment.new
160
+ payment.source = check
161
+ payment.order = order
162
+ payment.payment_method = gateway
163
+ payment.amount = 98.55
164
+ payment.state = 'pending'
165
+ payment.response_code = '12345'
166
+ payment
167
+ end
168
+
169
+ after do
170
+ payment.capture!
171
+ end
172
+
173
+ let!(:success_response) do
174
+ double('success_response',
175
+ success?: true,
176
+ authorization: '123',
177
+ avs_result: { 'code' => 'avs-code' },
178
+ cvv_result: { 'code' => 'cvv-code', 'message' => 'CVV Result' },
179
+ params: {})
180
+ end
181
+
182
+ it 'gets correct amount' do
183
+ provider.should_receive(:capture).with(9855, '12345', anything).and_return(success_response)
184
+ end
185
+ end
186
+ end
@@ -182,6 +182,7 @@ describe Spree::Gateway::StripeGateway do
182
182
 
183
183
  let!(:success_response) do
184
184
  double('success_response', :success? => true,
185
+ :params => {},
185
186
  :authorization => '123',
186
187
  :avs_result => { 'code' => 'avs-code' },
187
188
  :cvv_result => { 'code' => 'cvv-code', 'message' => "CVV Result"})
@@ -1,67 +1,10 @@
1
- require 'simplecov'
2
- SimpleCov.start 'rails'
1
+ # Configure Rails Environment
2
+ ENV['RAILS_ENV'] = 'test'
3
3
 
4
- ENV["RAILS_ENV"] = "test"
4
+ require File.expand_path('../dummy/config/environment.rb', __FILE__)
5
5
 
6
- require File.expand_path("../dummy/config/environment.rb", __FILE__)
6
+ require 'spree_dev_tools/rspec/spec_helper'
7
7
 
8
- require 'rspec/rails'
9
- require 'rspec/active_model/mocks'
10
- require 'capybara/rspec'
11
- require 'capybara/rails'
12
- require 'capybara-screenshot/rspec'
13
- require "selenium-webdriver"
14
- require 'webdrivers'
15
- require 'database_cleaner'
16
- require 'ffaker'
17
- require 'rspec/active_model/mocks'
18
- require 'pry'
19
-
20
- Dir[File.join(File.dirname(__FILE__), "support", "**", "*.rb")].each { |f| require f }
21
-
22
- require 'spree/testing_support/factories'
23
- require 'spree/testing_support/order_walkthrough'
24
- require 'spree/testing_support/preferences'
25
- require 'spree/testing_support/capybara_ext'
26
-
27
- FactoryBot.find_definitions
28
-
29
- RSpec.configure do |config|
30
- config.infer_spec_type_from_file_location!
31
- config.mock_with :rspec do |mock|
32
- mock.syntax = [:should, :expect]
33
- end
34
- config.raise_errors_for_deprecations!
35
- config.use_transactional_fixtures = false
36
- #config.filter_run focus: true
37
- #config.filter_run_excluding slow: true
38
-
39
- config.include FactoryBot::Syntax::Methods
40
- config.include Spree::TestingSupport::Preferences
41
-
42
- config.before :suite do
43
- DatabaseCleaner.strategy = :transaction
44
- DatabaseCleaner.clean_with :truncation
45
- end
46
-
47
- config.before do
48
- DatabaseCleaner.strategy = RSpec.current_example.metadata[:js] ? :truncation : :transaction
49
- DatabaseCleaner.start
50
- reset_spree_preferences
51
- create(:store)
52
- end
53
-
54
- config.after do
55
- DatabaseCleaner.clean
56
- end
57
-
58
- Capybara.register_driver :chrome do |app|
59
- Capybara::Selenium::Driver.new app,
60
- browser: :chrome,
61
- options: Selenium::WebDriver::Chrome::Options.new(
62
- args: %w[no-sandbox disable-dev-shm-usage disable-popup-blocking headless disable-gpu window-size=1920,1080 --enable-features=NetworkService,NetworkServiceInProcess --disable-features=VizDisplayCompositor],
63
- log_level: :error
64
- )
65
- end
66
- Capybara.javascript_driver = :chrome
67
- end
8
+ # Requires supporting ruby files with custom matchers and macros, etc,
9
+ # in spec/support/ and its subdirectories.
10
+ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].sort.each { |f| require f }