spree_gateway 3.8.0 → 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.
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