spree_gateway 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '015491170e10b6b22d98da98f5b8e466ae2df12474e5029bff6bd8926d86732e'
4
- data.tar.gz: 39713d557a03b1170dc36f4fcf4a6ec2e4fecb45b3a80376b00a23fd396c200b
3
+ metadata.gz: c0f581ca4a050e9c3bc878e37aedafd1ccd53c12297669d09574d5934a704e50
4
+ data.tar.gz: 73f289990e676a949d16e70529b0f97f0121a748b4be13d996678dd6587c1140
5
5
  SHA512:
6
- metadata.gz: c9d5003e95dc31701dbea5239a10dc587300d27659f29ea6deba81a309df2d24729ec3259d253d99f4c327c7f8889bc52b28dc8015d785fbde376a128d05a6f1
7
- data.tar.gz: 11a5083fba9163eabd567cef8173d949d6f6ac5b86a97f3c65281671d3fd82f6ed93d2f4626c2ac39e7ae71d16cc559735a9d5eb4024960041a1ba2128997ba8
6
+ metadata.gz: b9e8ad6df9dc8a0f595a20257b023f9269b70ce2f420dafa8cbc41b00220be5baaa1d3aa8db7eab69bc18e5b560d792cb4c4bb53a57e7421f7f894bf31d0f558
7
+ data.tar.gz: 6293ad5f7ddf84fd1a643637c2f3a2c18cb9e11a102d00aba665649c7738a4ff695a5bf9a9bb430fa5dc5f3e37a77fc5b6a705a23cb2e80deb7535ffc4d568fd
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module CheckoutControllerDecorator
3
+ def self.prepended(base)
4
+ base.before_action :process_payments_and_set_keys, only: :edit, if: proc { params[:state] == 'payment_confirm' }
5
+ end
6
+
7
+ def process_payments_and_set_keys
8
+ @order.tap do |order|
9
+ order.process_payments!
10
+ order.reload.payments.valid.where.not(intent_client_key: nil).last.tap do |payment|
11
+ @client_secret = payment.intent_client_key
12
+ @pk_key = payment.payment_method.preferred_publishable_key
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ ::Spree::CheckoutController.prepend Spree::CheckoutControllerDecorator
@@ -1,7 +1,57 @@
1
1
  module Spree
2
2
  class Gateway::StripeElementsGateway < Gateway::StripeGateway
3
+ preference :intents, :boolean, default: true
4
+
3
5
  def method_type
4
6
  'stripe_elements'
5
7
  end
8
+
9
+ def provider_class
10
+ if get_preference(:intents)
11
+ ActiveMerchant::Billing::StripePaymentIntentsGateway
12
+ else
13
+ ActiveMerchant::Billing::StripeGateway
14
+ end
15
+ end
16
+
17
+ def create_profile(payment)
18
+ return unless payment.source.gateway_customer_profile_id.nil?
19
+
20
+ options = {
21
+ email: payment.order.email,
22
+ login: preferred_secret_key
23
+ }.merge! address_for(payment)
24
+
25
+ source = update_source!(payment.source)
26
+ creditcard = source.gateway_payment_profile_id.present? ? source.gateway_payment_profile_id : source
27
+ response = provider.store(creditcard, options)
28
+ if response.success?
29
+ if get_preference(:intents)
30
+ payment.source.update!(
31
+ cc_type: payment.source.cc_type,
32
+ gateway_customer_profile_id: response.params['id'],
33
+ gateway_payment_profile_id: response.params['sources']['data'].first['id']
34
+ )
35
+ else
36
+ response_cc_type = response.params['sources']['data'].first['brand']
37
+ cc_type = CARD_TYPE_MAPPING.include?(response_cc_type) ? CARD_TYPE_MAPPING[response_cc_type] : payment.source.cc_type
38
+ payment.source.update!({
39
+ cc_type: cc_type, # side-effect of update_source!
40
+ gateway_customer_profile_id: response.params['id'],
41
+ gateway_payment_profile_id: response.params['default_source'] || response.params['default_card']
42
+ })
43
+ end
44
+ else
45
+ payment.send(:gateway_error, response.message)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def options_for_purchase_or_auth(money, creditcard, gateway_options)
52
+ money, creditcard, options = super
53
+ options[:execute_threed] = get_preference(:intents)
54
+ return money, creditcard, options
55
+ end
6
56
  end
7
57
  end
@@ -54,6 +54,7 @@ module Spree
54
54
 
55
55
  def create_profile(payment)
56
56
  return unless payment.source.gateway_customer_profile_id.nil?
57
+
57
58
  options = {
58
59
  email: payment.order.email,
59
60
  login: preferred_secret_key,
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module OrderDecorator
3
+ def self.prepended(base)
4
+ return if base.checkout_steps.key?(:payment_confirm)
5
+
6
+ base.insert_checkout_step(
7
+ :payment_confirm,
8
+ before: :complete,
9
+ if: ->(order) { order.intents? }
10
+ )
11
+ end
12
+
13
+ def process_payments!
14
+ # Payments are processed in confirm_payment step where after successful
15
+ # 3D Secure authentication `intent_client_key` is saved for payment.
16
+ # In case authentication is unsuccessful, `intent_client_key` is removed.
17
+ return if intents? && payments.valid.last.intent_client_key.present?
18
+
19
+ super
20
+ end
21
+
22
+ def intents?
23
+ payments.valid.map { |p| p.payment_method&.has_preference?(:intents) && p.payment_method&.get_preference(:intents) }.any?
24
+ end
25
+ end
26
+ end
27
+
28
+ Spree::Order.prepend(Spree::OrderDecorator)
@@ -1,5 +1,10 @@
1
1
  module Spree
2
2
  module PaymentDecorator
3
+ def handle_response(response, success_state, failure_state)
4
+ self.intent_client_key = response.params['client_secret'] if response.params['client_secret'] && response.success?
5
+ super
6
+ end
7
+
3
8
  def verify!(**options)
4
9
  process_verification(options)
5
10
  end
@@ -26,4 +31,4 @@ module Spree
26
31
  end
27
32
  end
28
33
 
29
- Spree::Payment.prepend Spree::PaymentDecorator
34
+ Spree::Payment.prepend(Spree::PaymentDecorator)
@@ -0,0 +1,34 @@
1
+ <div id='errorBox' class='errorExplanation alert alert-danger' style='display:none'></div>
2
+ <div id='successBox' class='alert alert-success' style='display:none'></div>
3
+ <div id='infoBox' class='alert alert-info'><%= t('spree.please_wait_for_confirmation_popup') %></div>
4
+
5
+ <% if @client_secret.present? && @pk_key.present? %>
6
+ <script type="text/javascript" src="https://js.stripe.com/v3/"></script>
7
+ <script>
8
+ var form = document.getElementById('checkout_form_payment_confirm');
9
+
10
+ function confirmCardPaymentResponseHandler(response) {
11
+ $.post("/api/v2/storefront/intents/handle_response", { response: response, order_token: "<%= @order.token %>" }).done(function (result) {
12
+ form.elements["commit"].disabled = false;
13
+ $('#successBox').html(result.message);
14
+ $('#successBox').show();
15
+ form.submit();
16
+ }).fail(function(result) {
17
+ form.elements["commit"].disabled = false;
18
+ $('#errorBox').html(result.responseJSON.error);
19
+ $('#errorBox').show();
20
+ });
21
+ }
22
+
23
+ var stripeElements = Stripe("<%= @pk_key %>");
24
+ stripeElements.confirmCardPayment("<%= @client_secret %>").then(function(result) {
25
+ $('#infoBox').hide();
26
+ confirmCardPaymentResponseHandler(result);
27
+ });
28
+
29
+ document.addEventListener('DOMContentLoaded', function(){
30
+ form.elements["commit"].value = "continue"
31
+ form.elements["commit"].disabled = true
32
+ });
33
+ </script>
34
+ <% end %>
@@ -10,6 +10,10 @@ en:
10
10
  spree:
11
11
  check: Check
12
12
  payment_has_been_cancelled: The payment has been cancelled.
13
+ please_wait_for_confirmation_popup: Please wait for payment confirmation popup to appear.
14
+ payment_successfully_authorized: The payment was successfully authorized.
15
+ order_state:
16
+ payment_confirm: Verify payment
13
17
  log_entry:
14
18
  braintree:
15
19
  message: Message
@@ -4,3 +4,15 @@
4
4
  Rails.application.routes.draw do
5
5
  get '/.well-known/apple-developer-merchantid-domain-association' => 'spree/apple_pay_domain_verification#show'
6
6
  end
7
+
8
+ Spree::Core::Engine.add_routes do
9
+ namespace :api, defaults: { format: 'json' } do
10
+ namespace :v2 do
11
+ namespace :storefront do
12
+ namespace :intents do
13
+ post :handle_response
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ class AddIntentKeyToPayment < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :spree_payments, :intent_client_key, :string
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class IntentsController < ::Spree::Api::V2::BaseController
6
+ include Spree::Api::V2::Storefront::OrderConcern
7
+
8
+ def handle_response
9
+ if params['response']['error']
10
+ invalidate_payment
11
+ render_error_payload(params['response']['error']['message'])
12
+ else
13
+ render_serialized_payload { { message: I18n.t('spree.payment_successfully_authorized') } }
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def invalidate_payment
20
+ payment = spree_current_order.payments.find_by!(response_code: params['response']['error']['payment_intent']['id'])
21
+ payment.update(state: 'failed', intent_client_key: nil)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,3 +2,4 @@ require 'spree_core'
2
2
  require 'spree_gateway/engine'
3
3
  require 'spree_gateway/version'
4
4
  require 'spree_extension'
5
+ require 'deface'
@@ -1,5 +1,5 @@
1
1
  module SpreeGateway
2
2
  def self.version
3
- '3.8.0'
3
+ '3.9.0'
4
4
  end
5
5
  end
@@ -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,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
@@ -175,7 +175,8 @@ describe Spree::Gateway::StripeAchGateway do
175
175
  success?: true,
176
176
  authorization: '123',
177
177
  avs_result: { 'code' => 'avs-code' },
178
- cvv_result: { 'code' => 'cvv-code', 'message' => 'CVV Result' })
178
+ cvv_result: { 'code' => 'cvv-code', 'message' => 'CVV Result' },
179
+ params: {})
179
180
  end
180
181
 
181
182
  it 'gets correct amount' do
@@ -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"})
@@ -0,0 +1,10 @@
1
+ def within_stripe_3ds_popup
2
+ using_wait_time(10) do
3
+ within_frame 0 do
4
+ within_frame 0 do
5
+ expect(page).to have_text('3D Secure 2 Test Page', normalize_ws: true)
6
+ yield
7
+ end
8
+ end
9
+ end
10
+ end
@@ -21,11 +21,12 @@ Gem::Specification.new do |s|
21
21
  s.require_path = 'lib'
22
22
  s.requirements << 'none'
23
23
 
24
- spree_version = '>= 3.1.0', '< 5.0'
24
+ spree_version = '>= 3.7.0', '< 5.0'
25
25
  s.add_dependency 'spree_core', spree_version
26
26
  s.add_dependency 'spree_extension'
27
+ s.add_dependency 'deface'
27
28
 
28
- s.add_development_dependency 'braintree'
29
+ s.add_development_dependency 'braintree', '~>2.78'
29
30
  s.add_development_dependency 'rspec-activemodel-mocks'
30
31
  s.add_development_dependency 'spree_dev_tools'
31
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_gateway
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.8.0
4
+ version: 3.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Spree Commerce
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-07 00:00:00.000000000 Z
11
+ date: 2020-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree_core
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.1.0
19
+ version: 3.7.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '5.0'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 3.1.0
29
+ version: 3.7.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '5.0'
@@ -45,19 +45,33 @@ dependencies:
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  - !ruby/object:Gem::Dependency
48
- name: braintree
48
+ name: deface
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
- type: :development
54
+ type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: braintree
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.78'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.78'
61
75
  - !ruby/object:Gem::Dependency
62
76
  name: rspec-activemodel-mocks
63
77
  requirement: !ruby/object:Gem::Requirement
@@ -105,6 +119,7 @@ files:
105
119
  - app/models/spree/apple_pay_payment_decorator.rb
106
120
  - app/models/spree/billing_integration.rb
107
121
  - app/models/spree/check.rb
122
+ - app/models/spree/checkout_controller_decorator.rb
108
123
  - app/models/spree/gateway/authorize_net.rb
109
124
  - app/models/spree/gateway/authorize_net_cim.rb
110
125
  - app/models/spree/gateway/balanced_gateway.rb
@@ -135,7 +150,9 @@ files:
135
150
  - app/models/spree/gateway/stripe_gateway.rb
136
151
  - app/models/spree/gateway/usa_epay_transaction.rb
137
152
  - app/models/spree/gateway/worldpay.rb
153
+ - app/models/spree/order_decorator.rb
138
154
  - app/models/spree/payment_decorator.rb
155
+ - app/views/spree/checkout/_payment_confirm.html.erb
139
156
  - config/initializers/inflections.rb
140
157
  - config/initializers/spree_permitted_attributes.rb
141
158
  - config/locales/bg.yml
@@ -149,10 +166,12 @@ files:
149
166
  - db/migrate/20131008221012_update_paypal_payment_method_type.rb
150
167
  - db/migrate/20131112133401_migrate_stripe_preferences.rb
151
168
  - db/migrate/20200317135551_add_spree_check_payment_source.rb
169
+ - db/migrate/20200422114908_add_intent_key_to_payment.rb
152
170
  - gemfiles/spree_4_1.gemfile
153
171
  - gemfiles/spree_4_2.gemfile
154
172
  - gemfiles/spree_master.gemfile
155
173
  - lib/active_merchant/billing/stripe_gateway_decorator.rb
174
+ - lib/controllers/spree/api/v2/storefront/intents_controller.rb
156
175
  - lib/controllers/spree/apple_pay_domain_verification_controller.rb
157
176
  - lib/generators/spree_gateway/install/install_generator.rb
158
177
  - lib/spree_gateway.rb
@@ -179,6 +198,7 @@ files:
179
198
  - spec/factories/check_factory.rb
180
199
  - spec/features/admin/stripe_elements_payment_spec.rb
181
200
  - spec/features/stripe_checkout_spec.rb
201
+ - spec/features/stripe_elements_3ds_checkout_spec.rb
182
202
  - spec/models/gateway/authorize_net_cim_spec.rb
183
203
  - spec/models/gateway/authorize_net_spec.rb
184
204
  - spec/models/gateway/balanced_gateway_spec.rb
@@ -209,12 +229,13 @@ files:
209
229
  - spec/requests/apple_pay_domain_verification.rb
210
230
  - spec/spec_helper.rb
211
231
  - spec/support/wait_for_stripe.rb
232
+ - spec/support/within_stripe_3ds_popup.rb
212
233
  - spree_gateway.gemspec
213
234
  homepage: https://spreecommerce.org
214
235
  licenses:
215
236
  - BSD-3-Clause
216
237
  metadata: {}
217
- post_install_message:
238
+ post_install_message:
218
239
  rdoc_options: []
219
240
  require_paths:
220
241
  - lib
@@ -231,13 +252,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
252
  requirements:
232
253
  - none
233
254
  rubygems_version: 3.1.2
234
- signing_key:
255
+ signing_key:
235
256
  specification_version: 4
236
257
  summary: Additional Payment Gateways for Spree Commerce
237
258
  test_files:
238
259
  - spec/factories/check_factory.rb
239
260
  - spec/features/admin/stripe_elements_payment_spec.rb
240
261
  - spec/features/stripe_checkout_spec.rb
262
+ - spec/features/stripe_elements_3ds_checkout_spec.rb
241
263
  - spec/models/gateway/authorize_net_cim_spec.rb
242
264
  - spec/models/gateway/authorize_net_spec.rb
243
265
  - spec/models/gateway/balanced_gateway_spec.rb
@@ -268,3 +290,4 @@ test_files:
268
290
  - spec/requests/apple_pay_domain_verification.rb
269
291
  - spec/spec_helper.rb
270
292
  - spec/support/wait_for_stripe.rb
293
+ - spec/support/within_stripe_3ds_popup.rb