solidus_stripe 3.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +5 -0
  4. data/CHANGELOG.md +58 -2
  5. data/Gemfile +7 -0
  6. data/LICENSE +2 -2
  7. data/README.md +26 -8
  8. data/Rakefile +1 -1
  9. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-cart-page-checkout.js +42 -9
  10. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-intents.js +3 -2
  11. data/app/assets/javascripts/spree/frontend/solidus_stripe/stripe-payment-request-button-shared.js +56 -20
  12. data/app/controllers/solidus_stripe/intents_controller.rb +23 -18
  13. data/app/decorators/models/spree/refund_decorator.rb +9 -0
  14. data/app/models/solidus_stripe/address_from_params_service.rb +5 -2
  15. data/app/models/solidus_stripe/create_intents_payment_service.rb +113 -0
  16. data/app/models/spree/payment_method/stripe_credit_card.rb +16 -1
  17. data/bin/r +13 -0
  18. data/bin/rake +7 -0
  19. data/bin/sandbox +84 -0
  20. data/bin/sandbox_rails +18 -0
  21. data/bin/setup +1 -1
  22. data/config/routes.rb +4 -1
  23. data/lib/generators/solidus_stripe/install/install_generator.rb +7 -3
  24. data/lib/solidus_stripe/engine.rb +2 -2
  25. data/lib/solidus_stripe/factories.rb +4 -0
  26. data/lib/solidus_stripe/version.rb +1 -1
  27. data/lib/views/frontend/spree/checkout/payment/v3/_form_elements.html.erb +0 -1
  28. data/solidus_stripe.gemspec +34 -37
  29. data/spec/features/stripe_checkout_spec.rb +101 -48
  30. data/spec/models/solidus_stripe/address_from_params_service_spec.rb +19 -5
  31. data/spec/models/solidus_stripe/create_intents_payment_service_spec.rb +127 -0
  32. data/spec/models/spree/payment_method/stripe_credit_card_spec.rb +44 -1
  33. data/spec/spec_helper.rb +4 -1
  34. metadata +23 -16
  35. data/LICENSE.md +0 -26
  36. data/app/models/solidus_stripe/create_intents_order_service.rb +0 -70
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module RefundDecorator
5
+ attr_reader :response
6
+
7
+ Spree::Refund.prepend(self)
8
+ end
9
+ end
@@ -4,7 +4,7 @@ module SolidusStripe
4
4
  class AddressFromParamsService
5
5
  attr_reader :address_params, :user
6
6
 
7
- def initialize(address_params, user)
7
+ def initialize(address_params, user = nil)
8
8
  @address_params, @user = address_params, user
9
9
  end
10
10
 
@@ -43,7 +43,10 @@ module SolidusStripe
43
43
  end
44
44
 
45
45
  def state
46
- @state ||= country.states.find_by_abbr(address_params[:region])
46
+ @state ||= begin
47
+ region = address_params[:region]
48
+ country.states.find_by(abbr: region) || country.states.find_by(name: region)
49
+ end
47
50
  end
48
51
 
49
52
  def default_attributes
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusStripe
4
+ class CreateIntentsPaymentService
5
+ attr_reader :intent_id, :stripe, :controller
6
+
7
+ delegate :request, :current_order, :params, to: :controller
8
+
9
+ def initialize(intent_id, stripe, controller)
10
+ @intent_id, @stripe, @controller = intent_id, stripe, controller
11
+ end
12
+
13
+ def call
14
+ invalidate_previous_payment_intents_payments
15
+ if (payment = create_payment)
16
+ description = "Solidus Order ID: #{payment.gateway_order_identifier}"
17
+ stripe.update_intent(nil, intent_id, nil, description: description)
18
+ true
19
+ else
20
+ invalidate_current_payment_intent
21
+ false
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def intent
28
+ @intent ||= stripe.show_intent(intent_id, {})
29
+ end
30
+
31
+ def invalidate_current_payment_intent
32
+ stripe.cancel(intent_id)
33
+ end
34
+
35
+ def invalidate_previous_payment_intents_payments
36
+ if stripe.v3_intents?
37
+ current_order.payments.pending.where(payment_method: stripe).each(&:void_transaction!)
38
+ end
39
+ end
40
+
41
+ def create_payment
42
+ Spree::OrderUpdateAttributes.new(
43
+ current_order,
44
+ payment_params,
45
+ request_env: request.headers.env
46
+ ).apply
47
+
48
+ created_payment = Spree::Payment.find_by(response_code: intent_id)
49
+ created_payment&.tap { |payment| payment.update!(state: :pending) }
50
+ end
51
+
52
+ def payment_params
53
+ {
54
+ payments_attributes: [{
55
+ payment_method_id: stripe.id,
56
+ amount: current_order.total,
57
+ response_code: intent_id,
58
+ source_attributes: {
59
+ month: intent_card['exp_month'],
60
+ year: intent_card['exp_year'],
61
+ cc_type: intent_card['brand'],
62
+ last_digits: intent_card['last4'],
63
+ gateway_payment_profile_id: intent_customer_profile,
64
+ name: card_holder_name || address_full_name,
65
+ address_attributes: address_attributes
66
+ }
67
+ }]
68
+ }
69
+ end
70
+
71
+ def intent_card
72
+ intent_data['payment_method_details']['card']
73
+ end
74
+
75
+ def intent_customer_profile
76
+ intent.params['payment_method']
77
+ end
78
+
79
+ def card_holder_name
80
+ (html_payment_source_data['name'] || intent_data['billing_details']['name']).presence
81
+ end
82
+
83
+ def intent_data
84
+ intent.params['charges']['data'][0]
85
+ end
86
+
87
+ def form_data
88
+ params[:form_data]
89
+ end
90
+
91
+ def html_payment_source_data
92
+ if form_data.is_a?(String)
93
+ data = Rack::Utils.parse_nested_query(form_data)
94
+ data['payment_source'][stripe.id.to_s]
95
+ else
96
+ {}
97
+ end
98
+ end
99
+
100
+ def address_attributes
101
+ html_payment_source_data['address_attributes'] || SolidusStripe::AddressFromParamsService.new(form_data).call.attributes
102
+ end
103
+
104
+ def address_full_name
105
+ current_order.bill_address&.full_name || form_data[:recipient]
106
+ end
107
+
108
+ def update_stripe_payment_description
109
+ description = "Solidus Order ID: #{payment.gateway_order_identifier}"
110
+ stripe.update_intent(nil, intent_id, nil, description: description)
111
+ end
112
+ end
113
+ end
@@ -15,7 +15,7 @@ module Spree
15
15
  'Visa' => 'visa'
16
16
  }
17
17
 
18
- delegate :create_intent, :update_intent, :confirm_intent, to: :gateway
18
+ delegate :create_intent, :update_intent, :confirm_intent, :show_intent, to: :gateway
19
19
 
20
20
  def stripe_config(order)
21
21
  {
@@ -81,6 +81,21 @@ module Spree
81
81
  gateway.void(response_code, {})
82
82
  end
83
83
 
84
+ def payment_intents_refund_reason
85
+ Spree::RefundReason.where(name: Spree::Payment::Cancellation::DEFAULT_REASON).first_or_create
86
+ end
87
+
88
+ def try_void(payment)
89
+ if v3_intents? && payment.completed?
90
+ payment.refunds.create!(
91
+ amount: payment.credit_allowed,
92
+ reason: payment_intents_refund_reason
93
+ ).response
94
+ else
95
+ void(payment.response_code, nil, nil)
96
+ end
97
+ end
98
+
84
99
  def cancel(response_code)
85
100
  gateway.void(response_code, {})
86
101
  end
data/bin/r ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('..', __dir__)
6
+ ENGINE_PATH = File.expand_path('../lib/solidus_stripe/engine', __dir__)
7
+
8
+ # Set up gems listed in the Gemfile.
9
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
10
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11
+
12
+ require 'rails/all'
13
+ require 'rails/engine/commands'
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+ require "bundler/setup"
6
+
7
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ case "$DB" in
6
+ postgres|postgresql)
7
+ RAILSDB="postgresql"
8
+ ;;
9
+ mysql)
10
+ RAILSDB="mysql"
11
+ ;;
12
+ sqlite|'')
13
+ RAILSDB="sqlite3"
14
+ ;;
15
+ *)
16
+ echo "Invalid DB specified: $DB"
17
+ exit 1
18
+ ;;
19
+ esac
20
+
21
+ if [ ! -z $SOLIDUS_BRANCH ]
22
+ then
23
+ BRANCH=$SOLIDUS_BRANCH
24
+ else
25
+ BRANCH="master"
26
+ fi
27
+
28
+ extension_name="solidus_stripe"
29
+
30
+ # Stay away from the bundler env of the containing extension.
31
+ function unbundled {
32
+ ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@
33
+ }
34
+
35
+ rm -rf ./sandbox
36
+ unbundled bundle exec rails new sandbox --database="$RAILSDB" \
37
+ --skip-bundle \
38
+ --skip-git \
39
+ --skip-keeps \
40
+ --skip-rc \
41
+ --skip-spring \
42
+ --skip-test \
43
+ --skip-javascript
44
+
45
+ if [ ! -d "sandbox" ]; then
46
+ echo 'sandbox rails application failed'
47
+ exit 1
48
+ fi
49
+
50
+ cd ./sandbox
51
+ cat <<RUBY >> Gemfile
52
+ gem 'solidus', github: 'solidusio/solidus', branch: '$BRANCH'
53
+ gem 'solidus_auth_devise', '>= 2.1.0'
54
+ gem 'rails-i18n'
55
+ gem 'solidus_i18n'
56
+
57
+ gem '$extension_name', path: '..'
58
+
59
+ group :test, :development do
60
+ platforms :mri do
61
+ gem 'pry-byebug'
62
+ end
63
+ end
64
+ RUBY
65
+
66
+ unbundled bundle install --gemfile Gemfile
67
+
68
+ unbundled bundle exec rake db:drop db:create
69
+
70
+ unbundled bundle exec rails generate spree:install \
71
+ --auto-accept \
72
+ --user_class=Spree::User \
73
+ --enforce_available_locales=true \
74
+ --with-authentication=false \
75
+ $@
76
+
77
+ unbundled bundle exec rails generate solidus:auth:install
78
+
79
+ echo
80
+ echo "🚀 Sandbox app successfully created for $extension_name!"
81
+ echo "🚀 Using $RAILSDB and Solidus $BRANCH"
82
+ echo "🚀 Use 'export DB=[postgres|mysql|sqlite]' to control the DB adapter"
83
+ echo "🚀 Use 'export SOLIDUS_BRANCH=<BRANCH-NAME>' to control the Solidus version"
84
+ echo "🚀 This app is intended for test purposes."
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ app_root = 'sandbox'
6
+
7
+ unless File.exist? "#{app_root}/bin/rails"
8
+ warn 'Creating the sandbox app...'
9
+ Dir.chdir "#{__dir__}/.." do
10
+ system "#{__dir__}/sandbox" or begin # rubocop:disable Style/AndOr
11
+ warn 'Automatic creation of the sandbox app failed'
12
+ exit 1
13
+ end
14
+ end
15
+ end
16
+
17
+ Dir.chdir app_root
18
+ exec 'bin/rails', *ARGV
data/bin/setup CHANGED
@@ -5,4 +5,4 @@ set -vx
5
5
 
6
6
  gem install bundler --conservative
7
7
  bundle update
8
- bundle exec rake extension:test_app
8
+ bin/rake clobber
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Spree::Core::Engine.routes.draw do
2
4
  # route to a deprecated controller, will be removed in the future:
3
5
  post '/stripe/confirm_payment', to: 'stripe#confirm_payment'
4
6
 
5
7
  # payment intents routes:
6
- post '/stripe/confirm_intents', to: '/solidus_stripe/intents#confirm'
8
+ post '/stripe/create_intent', to: '/solidus_stripe/intents#create_intent'
9
+ post '/stripe/create_payment', to: '/solidus_stripe/intents#create_payment'
7
10
 
8
11
  # payment request routes:
9
12
  post '/stripe/shipping_rates', to: '/solidus_stripe/payment_request#shipping_rates'
@@ -13,12 +13,16 @@ module SolidusStripe
13
13
  run 'bundle exec rake railties:install:migrations FROM=solidus_stripe'
14
14
  end
15
15
 
16
+ def add_migrations
17
+ run 'bin/rails railties:install:migrations FROM=solidus_stripe'
18
+ end
19
+
16
20
  def run_migrations
17
- run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
21
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Metrics/LineLength
18
22
  if run_migrations
19
- run 'bundle exec rake db:migrate'
23
+ run 'bin/rails db:migrate'
20
24
  else
21
- puts 'Skipping rake db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
25
+ puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output
22
26
  end
23
27
  end
24
28
 
@@ -4,9 +4,9 @@ require 'spree/core'
4
4
 
5
5
  module SolidusStripe
6
6
  class Engine < Rails::Engine
7
- include SolidusSupport::EngineExtensions::Decorators
7
+ include SolidusSupport::EngineExtensions
8
8
 
9
- isolate_namespace Spree
9
+ isolate_namespace ::Spree
10
10
 
11
11
  engine_name 'solidus_stripe'
12
12
 
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusStripe
4
- VERSION = "3.1.0"
4
+ VERSION = "4.2.0"
5
5
  end
@@ -1,6 +1,5 @@
1
1
  <div id="payment-request-button" data-stripe-config="<%= payment_method.stripe_config(current_order).to_json %>" data-v3-api="<%= stripe_v3_api %>"></div>
2
2
 
3
- <%= image_tag 'credit_cards/credit_card.gif', id: 'credit-card-image' %>
4
3
  <% param_prefix = "payment_source[#{payment_method.id}]" %>
5
4
 
6
5
  <div class="field field-required">
@@ -1,40 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- $:.push File.expand_path('lib', __dir__)
4
- require 'solidus_stripe/version'
5
-
6
- Gem::Specification.new do |s|
7
- s.name = 'solidus_stripe'
8
- s.version = SolidusStripe::VERSION
9
- s.summary = "Stripe Payment Method for Solidus"
10
- s.description = s.summary
11
- s.required_ruby_version = ">= 2.2"
12
-
13
- s.author = "Solidus Team"
14
- s.email = "contact@solidus.io"
15
- s.homepage = "https://solidus.io"
16
- s.license = 'BSD-3'
17
-
18
- if s.respond_to?(:metadata)
19
- s.metadata["homepage_uri"] = s.homepage if s.homepage
20
- s.metadata["source_code_uri"] = s.homepage if s.homepage
21
- end
22
-
23
- s.files = `git ls-files`.split("\n")
24
- s.test_files = `git ls-files -- spec/*`.split("\n")
25
- s.require_path = "lib"
26
- s.requirements << "none"
27
-
28
- s.bindir = "exe"
29
- s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
-
31
- s.add_dependency 'solidus_core', ['>= 2.3', '< 3']
32
- s.add_dependency 'solidus_support', '~> 0.4.0'
33
- # ActiveMerchant v1.58 through v1.59 introduced a breaking change
34
- # to the stripe gateway.
35
- #
36
- # This was resolved in v1.60, but we still need to skip 1.58 & 1.59.
37
- s.add_dependency "activemerchant", ">= 1.100" # includes "Stripe Payment Intents: Fix fallback for Store"
38
-
39
- s.add_development_dependency 'solidus_dev_support'
3
+ require_relative 'lib/solidus_stripe/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'solidus_stripe'
7
+ spec.version = SolidusStripe::VERSION
8
+ spec.authors = ['Solidus Team']
9
+ spec.email = 'contact@solidus.io'
10
+
11
+ spec.summary = 'Stripe Payment Method for Solidus'
12
+ spec.description = 'Stripe Payment Method for Solidus'
13
+ spec.homepage = 'https://github.com/solidusio/solidus_stripe#readme'
14
+ spec.license = 'BSD-3'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/solidusio/solidus_stripe'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/solidusio/solidus_stripe/blob/master/CHANGELOG.md'
19
+
20
+ spec.required_ruby_version = Gem::Requirement.new('~> 2.4')
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
25
+
26
+ spec.files = files.grep_v(%r{^(test|spec|features)/})
27
+ spec.test_files = files.grep(%r{^(test|spec|features)/})
28
+ spec.bindir = "exe"
29
+ spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency 'solidus_core', ['>= 2.3', '< 3']
33
+ spec.add_dependency 'solidus_support', '~> 0.5'
34
+ spec.add_dependency 'activemerchant', '>= 1.100'
35
+
36
+ spec.add_development_dependency 'solidus_dev_support'
40
37
  end