spree_gateway 3.7.4 → 3.9.2

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 +31 -38
  3. data/Appraisals +4 -10
  4. data/app/models/spree/check.rb +41 -0
  5. data/app/models/spree/gateway/stripe_ach_gateway.rb +60 -0
  6. data/app/models/spree/gateway/stripe_elements_gateway.rb +50 -0
  7. data/app/models/spree/gateway/stripe_gateway.rb +1 -0
  8. data/app/models/spree/order_decorator.rb +28 -0
  9. data/app/models/spree/payment_decorator.rb +34 -0
  10. data/app/views/spree/checkout/_payment_confirm.html.erb +34 -0
  11. data/config/initializers/spree_permitted_attributes.rb +5 -0
  12. data/config/locales/en.yml +23 -0
  13. data/config/routes.rb +12 -0
  14. data/db/migrate/20200317135551_add_spree_check_payment_source.rb +22 -0
  15. data/db/migrate/20200422114908_add_intent_key_to_payment.rb +5 -0
  16. data/gemfiles/{spree_3_5.gemfile → spree_4_1.gemfile} +1 -1
  17. data/gemfiles/{spree_4_0.gemfile → spree_4_2.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.rb +1 -0
  22. data/lib/spree_gateway/engine.rb +8 -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_views/_stripe_ach.html.erb +21 -0
  27. data/lib/views/backend/spree/admin/payments/source_views/_stripe_apple_pay.html.erb +1 -0
  28. data/lib/views/frontend/spree/checkout/payment/_stripe_ach.html.erb +81 -0
  29. data/lib/views/frontend/spree/checkout/payment/_stripe_ach_verify.html.erb +16 -0
  30. data/lib/views/frontend/spree/checkout/payment/_stripe_apple_pay.html.erb +10 -1
  31. data/lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb +2 -1
  32. data/spec/factories/check_factory.rb +10 -0
  33. data/spec/features/admin/stripe_elements_payment_spec.rb +13 -3
  34. data/spec/features/stripe_checkout_spec.rb +3 -0
  35. data/spec/features/stripe_elements_3ds_checkout_spec.rb +106 -0
  36. data/spec/models/gateway/stripe_ach_gateway_spec.rb +186 -0
  37. data/spec/models/gateway/stripe_gateway_spec.rb +1 -0
  38. data/spec/spec_helper.rb +7 -65
  39. data/spec/support/within_stripe_3ds_popup.rb +10 -0
  40. data/spree_gateway.gemspec +11 -22
  41. metadata +44 -271
  42. data/gemfiles/spree_3_7.gemfile +0 -9
  43. data/spec/support/capybara_helper.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ab0991265fe493eef93a0ee098892bd46682c56ab554ae773831ae2b154115b
4
- data.tar.gz: ea290e80beadd38e792c4218180bad4a65505ac038e7c5b21e19f0a18d63cb24
3
+ metadata.gz: b54b6ec2e6137ae258ac3d108c715f3a8bf2e534c8f3ee13946529831fdf6c8e
4
+ data.tar.gz: 95e413abf6077ee0ed9cf92d260be0ad44de0888334d94b6d60b8b68002e85fd
5
5
  SHA512:
6
- metadata.gz: 0ce78bd6a8b62af2de0ac1e73abb6844ea16385d3ff121c033070b6c965f1e87e52d7a3eced0d35ced01d127eef3057dd8132f561555ac89780363b179e2e40f
7
- data.tar.gz: 9a0ac382a6aa612965f0ad22950ec96a907c069c71a06e8f319987582ad64691cb718bbda349ddd7b7d6accb9bc23f548e43908fb157b692c13395d468c8f96c
6
+ metadata.gz: b94ebcae1a0004d780547e34f73764093a84ab49a36a6c31bbc5177b7094c6c609a545da6684d262eb65155550c8c6eb1eb528118a1258ef2b090512c862afa7
7
+ data.tar.gz: 2facdc1ebd849fe10fffb1b2ab0daebef9e90d7178bcad1a29fd3cd896935fc12c2f828dd48e01644557f3abf21320b76402c150ba8399b309fbbc5d9a83a668
@@ -1,51 +1,44 @@
1
- sudo: required
2
- dist: trusty
1
+ os: linux
2
+ dist: bionic
3
+
4
+ addons:
5
+ apt:
6
+ sources:
7
+ - google-chrome
8
+ packages:
9
+ - google-chrome-stable
10
+
11
+ services:
12
+ - mysql
13
+ - postgresql
3
14
 
4
15
  language: ruby
5
16
 
6
- addons:
7
- chrome: stable
8
- postgresql: 9.4
17
+ rvm:
18
+ - 2.6
9
19
 
10
20
  env:
11
- - DB=postgres
12
21
  - DB=mysql
22
+ - DB=postgres
13
23
 
14
24
  gemfile:
15
- - gemfiles/spree_3_5.gemfile
16
- - gemfiles/spree_3_7.gemfile
17
- - gemfiles/spree_4_0.gemfile
25
+ # - gemfiles/spree_4_1.gemfile
26
+ - gemfiles/spree_4_2.gemfile
18
27
  - gemfiles/spree_master.gemfile
19
28
 
20
- script:
21
- - bundle exec rake test_app
22
- - bundle exec rake spec
23
-
24
- rvm:
25
- - 2.5.1
26
- - 2.4.4
27
- - 2.3.8
28
-
29
- matrix:
30
- allow_failures:
31
- - gemfile: gemfiles/spree_master.gemfile
32
- exclude:
33
- - rvm: 2.3.8
34
- gemfile: gemfiles/spree_master.gemfile
35
- - rvm: 2.4.4
36
- gemfile: gemfiles/spree_master.gemfile
37
- - rvm: 2.3.8
38
- gemfile: gemfiles/spree_4_0.gemfile
39
- - rvm: 2.4.4
40
- gemfile: gemfiles/spree_4_0.gemfile
41
- - rvm: 2.5.1
42
- gemfile: gemfiles/spree_3_5.gemfile
29
+ jobs:
30
+ allow_failures:
31
+ - gemfile: gemfiles/spree_master.gemfile
43
32
 
44
33
  before_install:
45
34
  - mysql -u root -e "GRANT ALL ON *.* TO 'travis'@'%';"
46
- - wget -N https://chromedriver.storage.googleapis.com/2.35/chromedriver_linux64.zip -P ~/
47
- - unzip ~/chromedriver_linux64.zip -d ~/
48
- - rm ~/chromedriver_linux64.zip
49
- - sudo mv -f ~/chromedriver /usr/local/share/
50
- - sudo chmod +x /usr/local/share/chromedriver
51
- - sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver
35
+
36
+ before_script:
37
+ - CHROME_MAIN_VERSION=`google-chrome-stable --version | sed -E 's/(^Google Chrome |\.[0-9]+ )//g'`
38
+ - CHROMEDRIVER_VERSION=`curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_MAIN_VERSION"`
39
+ - curl "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" -O
40
+ - unzip chromedriver_linux64.zip -d ~/bin
41
+
42
+ script:
43
+ - bundle exec rake test_app
44
+ - bundle exec rake spec
data/Appraisals CHANGED
@@ -1,16 +1,10 @@
1
- appraise 'spree-3-5' do
2
- gem 'spree', '~> 3.5.0'
1
+ appraise 'spree-4-1' do
2
+ gem 'spree', '~> 4.1.0'
3
3
  gem 'rails-controller-testing'
4
4
  end
5
5
 
6
- appraise 'spree-3-7' do
7
- gem 'spree', '~> 3.7.0'
8
- gem 'sass-rails'
9
- gem 'rails-controller-testing'
10
- end
11
-
12
- appraise 'spree-4-0' do
13
- gem 'spree', '~> 4.0.0.rc2'
6
+ appraise 'spree-4-2' do
7
+ gem 'spree', '~> 4.2.0.beta'
14
8
  gem 'rails-controller-testing'
15
9
  end
16
10
 
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ class Check < Spree::Base
3
+
4
+ attr_accessor :imported
5
+
6
+ belongs_to :payment_method
7
+ belongs_to :user, class_name: Spree.user_class.to_s, foreign_key: 'user_id',
8
+ optional: true
9
+ has_many :payments, as: :source
10
+
11
+ scope :with_payment_profile, -> { where.not(gateway_customer_profile_id: nil) }
12
+
13
+ validates :account_holder_name, presence: true
14
+ validates :account_holder_type, presence: true, inclusion: { in: %w[Individual Company] }
15
+ validates :account_number, presence: true, numericality: { only_integer: true }
16
+ validates :routing_number, presence: true, numericality: { only_integer: true }
17
+
18
+ def has_payment_profile?
19
+ gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
20
+ end
21
+
22
+ def actions
23
+ %w[capture void credit]
24
+ end
25
+
26
+ def can_capture?(payment)
27
+ payment.pending? || payment.checkout?
28
+ end
29
+
30
+ # Indicates whether its possible to void the payment.
31
+ def can_void?(payment)
32
+ !payment.failed? && !payment.void?
33
+ end
34
+
35
+ # Indicates whether its possible to credit the payment. Note that most gateways require that the
36
+ # payment be settled first which generally happens within 12-24 hours of the transaction.
37
+ def can_credit?(payment)
38
+ payment.completed? && payment.credit_allowed > 0
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ module Spree
2
+ class Gateway::StripeAchGateway < Gateway::StripeGateway
3
+
4
+ def method_type
5
+ 'stripe_ach'
6
+ end
7
+
8
+ def payment_source_class
9
+ Check
10
+ end
11
+
12
+ def verify(source, **gateway_options)
13
+ provider.verify(source, gateway_options)
14
+ end
15
+
16
+ def create_profile(payment)
17
+ return unless payment.source&.gateway_customer_profile_id.nil?
18
+
19
+ options = {
20
+ email: payment.order.user&.email || payment.order.email,
21
+ login: preferred_secret_key,
22
+ }.merge! address_for(payment)
23
+
24
+ source = payment.source
25
+ bank_account = if source.gateway_payment_profile_id.present?
26
+ source.gateway_payment_profile_id
27
+ else
28
+ source
29
+ end
30
+
31
+ response = provider.store(bank_account, options)
32
+
33
+ if response.success?
34
+ payment.source.update!({
35
+ gateway_customer_profile_id: response.params['id'],
36
+ gateway_payment_profile_id: response.params['default_source'] || response.params['default_card']
37
+ })
38
+
39
+ else
40
+ payment.send(:gateway_error, response.message)
41
+ end
42
+ end
43
+
44
+ def supports?(_source)
45
+ true
46
+ end
47
+
48
+ def available_for_order?(order)
49
+ # Stripe ACH payments are supported only for US customers
50
+ # Therefore we need to check order's addresses
51
+ return unless order.ship_address_id && order.bill_address_id
52
+ return unless order.ship_address && order.bill_address_id
53
+
54
+ usa_id = ::Spree::Country.find_by(iso: 'US')&.id
55
+ return false unless usa_id
56
+
57
+ order.ship_address.country_id == usa_id && order.bill_address.country_id == usa_id
58
+ end
59
+ end
60
+ end
@@ -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)
@@ -0,0 +1,34 @@
1
+ module Spree
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
+
8
+ def verify!(**options)
9
+ process_verification(options)
10
+ end
11
+
12
+ private
13
+
14
+ def process_verification(**options)
15
+ protect_from_connection_error do
16
+ response = payment_method.verify(source, options)
17
+
18
+ record_response(response)
19
+
20
+ if response.success?
21
+ unless response.authorization.nil?
22
+ self.response_code = response.authorization
23
+
24
+ source.update(status: response.params['status'])
25
+ end
26
+ else
27
+ gateway_error(response)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
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 %>
@@ -0,0 +1,5 @@
1
+ module Spree
2
+ module PermittedAttributes
3
+ @@source_attributes += %i[account_number routing_number account_holder_type account_holder_name status]
4
+ end
5
+ end
@@ -1,7 +1,19 @@
1
1
  ---
2
2
  en:
3
+ activerecord:
4
+ attributes:
5
+ spree/check:
6
+ account_holder_name: Account Holder Name
7
+ account_holder_type: Account Holder Type
8
+ account_number: Account Number
9
+ routing_number: Routing Number
3
10
  spree:
11
+ check: Check
4
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
5
17
  log_entry:
6
18
  braintree:
7
19
  message: Message
@@ -21,3 +33,14 @@ en:
21
33
  cvv_result: CVV Result
22
34
  cvc_check: CVC Check
23
35
  address_zip_check: Address ZIP check
36
+ stripe:
37
+ ach:
38
+ account_holder_name: Account Holder Name
39
+ account_holder_type: Account Holder Type
40
+ routing_number: Routing Number
41
+ account_number: Account Number
42
+ verify_account_number: Verify Account Number
43
+ verify_bank_account: Verify Bank Account
44
+ first_deposit: First Deposit
45
+ second_deposit: Second Deposit
46
+ bank_transfer: bank_transfer
@@ -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