spree_gateway 3.7.4 → 3.9.3

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 (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