spree_gateway 3.7.4 → 3.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +36 -37
  3. data/Appraisals +3 -8
  4. data/app/models/spree/check.rb +41 -0
  5. data/app/models/spree/credit_card_decorator.rb +10 -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 +36 -0
  11. data/app/views/spree/checkout/_payment_confirm.html.erb +39 -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_5.gemfile → spree_4_1.gemfile} +1 -1
  18. data/lib/active_merchant/billing/stripe_gateway_decorator.rb +13 -0
  19. data/lib/controllers/spree/api/v2/storefront/intents_controller.rb +27 -0
  20. data/lib/spree_frontend/controllers/spree/checkout_controller_decorator.rb +19 -0
  21. data/lib/spree_gateway/engine.rb +8 -0
  22. data/lib/spree_gateway/version.rb +1 -1
  23. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_ach.html.erb +86 -0
  24. data/lib/views/backend/spree/admin/payments/source_forms/_stripe_apple_pay.html.erb +0 -0
  25. data/lib/views/backend/spree/admin/payments/source_views/_stripe_ach.html.erb +21 -0
  26. data/lib/views/backend/spree/admin/payments/source_views/_stripe_apple_pay.html.erb +1 -0
  27. data/lib/views/frontend/spree/checkout/payment/_stripe_ach.html.erb +81 -0
  28. data/lib/views/frontend/spree/checkout/payment/_stripe_ach_verify.html.erb +16 -0
  29. data/lib/views/frontend/spree/checkout/payment/_stripe_apple_pay.html.erb +10 -1
  30. data/lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb +2 -1
  31. data/spec/factories/check_factory.rb +10 -0
  32. data/spec/features/admin/stripe_elements_payment_spec.rb +32 -32
  33. data/spec/features/stripe_checkout_spec.rb +30 -19
  34. data/spec/features/stripe_elements_3ds_checkout_spec.rb +224 -0
  35. data/spec/models/gateway/stripe_ach_gateway_spec.rb +186 -0
  36. data/spec/models/gateway/stripe_gateway_spec.rb +1 -0
  37. data/spec/spec_helper.rb +7 -65
  38. data/spec/support/order_walktrough.rb +73 -0
  39. data/spec/support/within_stripe_3ds_popup.rb +10 -0
  40. data/spree_gateway.gemspec +10 -22
  41. metadata +44 -282
  42. data/gemfiles/spree_4_0.gemfile +0 -8
  43. data/spec/support/capybara_helper.rb +0 -15
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Stripe checkout", type: :feature do
3
+ describe "Stripe checkout", type: :feature, js: true do
4
4
  let!(:country) { create(:country, :states_required => true) }
5
5
  let!(:state) { create(:state, :country => country) }
6
6
  let!(:shipping_method) { create(:shipping_method) }
@@ -42,7 +42,7 @@ describe "Stripe checkout", type: :feature do
42
42
  end
43
43
 
44
44
  # This will pass the CC data to the server and the StripeGateway class handles it
45
- it "can process a valid payment (without JS)" do
45
+ it "can process a valid payment (without JS)", js: false do
46
46
  fill_in 'card_number', with: '4242 4242 4242 4242'
47
47
  fill_in 'card_code', with: '123'
48
48
  fill_in 'card_expiry', with: "01 / #{Time.current.year + 1}"
@@ -56,12 +56,10 @@ describe "Stripe checkout", type: :feature do
56
56
 
57
57
  # This will fetch a token from Stripe.com and then pass that to the webserver.
58
58
  # The server then processes the payment using that token.
59
- it "can process a valid payment (with JS)", :js => true do
60
- fill_in 'card_number', with: '4242 4242 4242 4242'
61
- # Otherwise ccType field does not get updated correctly
62
- page.execute_script("$('.cardNumber').trigger('change')")
59
+ it "can process a valid payment (with JS)" do
60
+ fill_in_with_force('card_number', with: "4242424242424242")
61
+ fill_in_with_force('card_expiry', with: "01 / #{Time.current.year + 1}")
63
62
  fill_in 'card_code', with: '123'
64
- fill_in 'card_expiry', with: "01 / #{Time.current.year + 1}"
65
63
  click_button "Save and Continue"
66
64
  wait_for_stripe # Wait for Stripe API to return + form to submit
67
65
  expect(page).to have_css('#checkout_form_confirm')
@@ -72,18 +70,23 @@ describe "Stripe checkout", type: :feature do
72
70
  expect(page).to have_content(order.number)
73
71
  end
74
72
 
75
- it "shows an error with an invalid credit card number", :js => true do
73
+ it "shows an error with an invalid credit card number" do
76
74
  # Card number is NOT valid. Fails Luhn checksum
77
75
  fill_in 'card_number', with: '4242 4242 4242 4249'
78
76
  click_button "Save and Continue"
79
77
  wait_for_stripe
80
- expect(page).to have_content("Your card number is incorrect")
81
- expect(page).to have_css('.has-error #card_number.error')
78
+ if Spree.version.to_f >= 3.7 and Spree.version.to_f <= 4.1
79
+ expect(page).to have_content("The card number is not a valid credit card number")
80
+ end
81
+ if Spree.version.to_f >= 4.2
82
+ expect(page).to have_content("Your card number is incorrect")
83
+ expect(page).to have_css('.has-error #card_number.error')
84
+ end
82
85
  end
83
86
 
84
- it "shows an error with invalid security fields", :js => true do
85
- fill_in 'card_number', with: '4242 4242 4242 4242'
86
- fill_in 'card_expiry', with: "01 / #{Time.current.year + 1}"
87
+ it "shows an error with invalid security fields" do
88
+ fill_in_with_force('card_number', with: "4242424242424242")
89
+ fill_in_with_force('card_expiry', with: "01 / #{Time.current.year + 1}")
87
90
  fill_in 'card_code', with: '99'
88
91
  click_button "Save and Continue"
89
92
  wait_for_stripe
@@ -91,9 +94,12 @@ describe "Stripe checkout", type: :feature do
91
94
  expect(page).to have_css('.has-error #card_code.error')
92
95
  end
93
96
 
94
- it "shows an error with invalid expiry month field", :js => true do
95
- fill_in 'card_number', with: '4242 4242 4242 4242'
96
- fill_in 'card_expiry', :with => "00 / #{Time.now.year + 1}"
97
+ # this scenario will not occur on Spree 4.2 due to swapping jquery.payment to cleave
98
+ # see https://github.com/spree/spree/pull/10363
99
+ it "shows an error with invalid expiry month field" do
100
+ skip if Spree.version.to_f >= 4.2
101
+ fill_in_with_force('card_number', with: "4242424242424242")
102
+ fill_in_with_force('card_expiry', with: "00 / #{Time.current.year + 1}")
97
103
  fill_in 'card_code', with: '123'
98
104
  click_button "Save and Continue"
99
105
  wait_for_stripe
@@ -101,9 +107,9 @@ describe "Stripe checkout", type: :feature do
101
107
  expect(page).to have_css('.has-error #card_expiry.error')
102
108
  end
103
109
 
104
- it "shows an error with invalid expiry year field", :js => true do
105
- fill_in 'card_number', with: '4242 4242 4242 4242'
106
- fill_in 'card_expiry', with: '12 / '
110
+ it "shows an error with invalid expiry year field" do
111
+ fill_in_with_force('card_number', with: "4242424242424242")
112
+ fill_in_with_force('card_expiry', with: "12 / ")
107
113
  fill_in 'card_code', with: '123'
108
114
  click_button "Save and Continue"
109
115
  wait_for_stripe
@@ -111,3 +117,8 @@ describe "Stripe checkout", type: :feature do
111
117
  expect(page).to have_css('.has-error #card_expiry.error')
112
118
  end
113
119
  end
120
+
121
+ def fill_in_with_force(locator, with:)
122
+ field_id = find_field(locator)[:id]
123
+ page.execute_script("document.getElementById('#{field_id}').value = '#{with}';")
124
+ end
@@ -0,0 +1,224 @@
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
+
34
+ if Spree.version.to_f >= 3.7 and Spree.version.to_f < 4.1
35
+ find("#checkout-link").click
36
+ else
37
+ click_link 'checkout'
38
+ click_button 'Place Order'
39
+ end
40
+ end
41
+
42
+ describe 'when intents are disabled' do
43
+ let(:preferred_intents) { false }
44
+
45
+ context 'and credit card does not require 3ds authentication' do
46
+ let(:card_number) { '4242424242424242' }
47
+
48
+ if Spree.version.to_f >= 3.7 and Spree.version.to_f < 4.1
49
+ it 'should place order without 3ds authentication', driver: :selenium_chrome_headless do
50
+ click_button 'Save and Continue'
51
+ click_button 'Save and Continue'
52
+
53
+ within_frame 0 do
54
+ fill_in 'cardnumber', with: card_number
55
+ fill_in 'exp-date', with: "01 / #{Time.current.strftime('%y').to_i + 3}"
56
+ fill_in 'cvc', with: "222"
57
+ end
58
+
59
+ click_button 'Save and Continue'
60
+ click_button 'Place Order'
61
+
62
+ expect(page).to have_content('Your order has been processed successfully')
63
+ order = Spree::Order.complete.last
64
+ expect(page.current_url).to include("/orders/#{order.number}")
65
+ expect(page).to have_content(order.number)
66
+ end
67
+ else
68
+ it 'should place order without 3ds authentication' do
69
+ expect(page).to have_content('Order placed successfully')
70
+ order = Spree::Order.complete.last
71
+ expect(page.current_url).to include("/orders/#{order.number}")
72
+ expect(page).to have_content(order.number)
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'and credit card does require 3ds authentication' do
78
+ let(:card_number) { '4000000000003220' }
79
+
80
+ if Spree.version.to_f >= 3.7 and Spree.version.to_f < 4.1
81
+ it 'should not place the order', driver: :selenium_chrome_headless do
82
+ click_button 'Save and Continue'
83
+ click_button 'Save and Continue'
84
+
85
+ within_frame 0 do
86
+ fill_in 'cardnumber', with: card_number
87
+ fill_in 'exp-date', with: "01 / #{Time.current.strftime('%y').to_i + 3}"
88
+ fill_in 'cvc', with: "222"
89
+ end
90
+
91
+ click_button 'Save and Continue'
92
+ click_button 'Place Order'
93
+
94
+ expect(page).to have_content('Your card was declined. This transaction requires authentication.')
95
+ expect(Spree::Order.complete.last).to be_nil
96
+ end
97
+
98
+ else
99
+ it 'should not place the order' do
100
+ expect(page).to have_content('Your card was declined. This transaction requires authentication.')
101
+ expect(Spree::Order.complete.last).to be_nil
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'when intents are enabled' do
108
+ let(:preferred_intents) { true }
109
+
110
+ context 'and credit card does not require 3ds authentication' do
111
+ let(:card_number) { '4242424242424242' }
112
+
113
+ if Spree.version.to_f >= 3.7 and Spree.version.to_f < 4.1
114
+ it 'should successfully place order without 3ds authentication', driver: :selenium_chrome_headless do
115
+ click_button 'Save and Continue'
116
+ click_button 'Save and Continue'
117
+
118
+ within_frame 0 do
119
+ fill_in 'cardnumber', with: card_number
120
+ fill_in 'exp-date', with: "01 / #{Time.current.strftime('%y').to_i + 3}"
121
+ fill_in 'cvc', with: "222"
122
+ end
123
+
124
+ click_button 'Save and Continue'
125
+ click_button 'Place Order'
126
+
127
+ expect(page).to have_content('Your order has been processed successfully')
128
+ order = Spree::Order.complete.last
129
+ expect(page.current_url).to include("/orders/#{order.number}")
130
+ expect(page).to have_content(order.number)
131
+ end
132
+ else
133
+ it 'should successfully place order without 3ds authentication' do
134
+ expect(page).to have_content('Order placed successfully')
135
+ order = Spree::Order.complete.last
136
+ expect(page.current_url).to include("/orders/#{order.number}")
137
+ expect(page).to have_content(order.number)
138
+ end
139
+ end
140
+ end
141
+
142
+ context 'when credit card does require 3ds authentication' do
143
+ let(:card_number) { '4000000000003220' }
144
+
145
+ context 'and authentication is successful' do
146
+ if Spree.version.to_f >= 3.7 and Spree.version.to_f < 4.1
147
+ it 'should place order after 3ds authentication', driver: :selenium_chrome_headless do
148
+ click_button 'Save and Continue'
149
+ click_button 'Save and Continue'
150
+
151
+ within_frame 0 do
152
+ fill_in 'cardnumber', with: card_number
153
+ fill_in 'exp-date', with: "01 / #{Time.current.strftime('%y').to_i + 3}"
154
+ fill_in 'cvc', with: "222"
155
+ end
156
+
157
+ click_button 'Save and Continue'
158
+ click_button 'Place Order'
159
+
160
+ within_stripe_3ds_popup do
161
+ click_button('Complete')
162
+ end
163
+
164
+ expect(page).to have_content('Your order has been processed successfully')
165
+ order = Spree::Order.complete.last
166
+ expect(page.current_url).to include("/orders/#{order.number}")
167
+ expect(page).to have_content(order.number)
168
+ end
169
+
170
+ else
171
+ it 'should place order after 3ds authentication' do
172
+ within_stripe_3ds_popup do
173
+ click_button('Complete')
174
+ end
175
+
176
+ expect(page).to have_content('Order placed successfully')
177
+ order = Spree::Order.complete.last
178
+ expect(page.current_url).to include("/orders/#{order.number}")
179
+ expect(page).to have_content(order.number)
180
+ end
181
+ end
182
+ end
183
+
184
+ context 'and authentication is unsuccessful' do
185
+
186
+ if Spree.version.to_f >= 3.7 and Spree.version.to_f < 4.1
187
+ it 'should not place order after 3ds authentication', driver: :selenium_chrome_headless do
188
+ click_button 'Save and Continue'
189
+ click_button 'Save and Continue'
190
+
191
+ within_frame 0 do
192
+ fill_in 'cardnumber', with: card_number
193
+ fill_in 'exp-date', with: "01 / #{Time.current.strftime('%y').to_i + 3}"
194
+ fill_in 'cvc', with: "222"
195
+ end
196
+
197
+ click_button 'Save and Continue'
198
+ click_button 'Place Order'
199
+
200
+ within_stripe_3ds_popup do
201
+ click_button('Fail')
202
+ end
203
+
204
+ expect(page).to_not have_content('Order placed successfully')
205
+ expect(page).to have_content('We are unable to authenticate your payment method.')
206
+ expect(page).to have_content('Please choose a different payment method and try again.')
207
+ expect(Spree::Order.complete.last).to be_nil
208
+ end
209
+ else
210
+ it 'should not place order after 3ds authentication' do
211
+ within_stripe_3ds_popup do
212
+ click_button('Fail')
213
+ end
214
+
215
+ expect(page).to_not have_content('Order placed successfully')
216
+ expect(page).to have_content('We are unable to authenticate your payment method.')
217
+ expect(page).to have_content('Please choose a different payment method and try again.')
218
+ expect(Spree::Order.complete.last).to be_nil
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ 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