spree_stripe_subscriptions 0.0.1
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 +7 -0
- data/.gitignore +91 -0
- data/.rubocop.yml +26 -0
- data/.travis.yml +37 -0
- data/Gemfile +12 -0
- data/LICENSE +26 -0
- data/README.md +72 -0
- data/Rakefile +21 -0
- data/app/controllers/spree/admin/stripe_configurations_controller.rb +6 -0
- data/app/controllers/spree/admin/stripe_customers_controller.rb +6 -0
- data/app/controllers/spree/admin/stripe_plans_controller.rb +36 -0
- data/app/controllers/spree/admin/stripe_subscription_events_controller.rb +6 -0
- data/app/controllers/spree/admin/stripe_subscriptions_controller.rb +6 -0
- data/app/controllers/spree/stripe_plans_controller.rb +20 -0
- data/app/controllers/spree/stripe_subscriptions_controller.rb +166 -0
- data/app/controllers/spree/stripe_webhooks_controller.rb +56 -0
- data/app/models/spree/stripe_configuration.rb +28 -0
- data/app/models/spree/stripe_customer.rb +30 -0
- data/app/models/spree/stripe_invoice.rb +21 -0
- data/app/models/spree/stripe_plan.rb +104 -0
- data/app/models/spree/stripe_subscription.rb +167 -0
- data/app/models/spree/stripe_subscription_event.rb +10 -0
- data/app/models/spree_stripe_subscriptions/configuration.rb +15 -0
- data/app/models/spree_stripe_subscriptions/user_decorator.rb +50 -0
- data/app/overrides/spree/admin/shared/_main_menu.rb +13 -0
- data/app/serializers/.gitkeep +0 -0
- data/app/services/.gitkeep +0 -0
- data/app/views/spree/admin/shared/sub_menu/_stripe_subscriptions.html.erb +8 -0
- data/app/views/spree/admin/stripe_configurations/_form.html.erb +42 -0
- data/app/views/spree/admin/stripe_configurations/edit.html.erb +17 -0
- data/app/views/spree/admin/stripe_configurations/index.html.erb +44 -0
- data/app/views/spree/admin/stripe_configurations/new.html.erb +13 -0
- data/app/views/spree/admin/stripe_customers/index.html.erb +36 -0
- data/app/views/spree/admin/stripe_plans/_form.html.erb +50 -0
- data/app/views/spree/admin/stripe_plans/edit.html.erb +15 -0
- data/app/views/spree/admin/stripe_plans/index.html.erb +59 -0
- data/app/views/spree/admin/stripe_plans/new.html.erb +15 -0
- data/app/views/spree/admin/stripe_subscription_events/index.html.erb +36 -0
- data/app/views/spree/admin/stripe_subscriptions/index.html.erb +40 -0
- data/app/views/spree/stripe_plans/index.html.erb +77 -0
- data/bin/rails +8 -0
- data/config/initializers/stripe.rb +11 -0
- data/config/locales/en.yml +80 -0
- data/config/routes.rb +27 -0
- data/db/migrate/20221130094411_add_spree_stripe_configuration_model.rb +14 -0
- data/db/migrate/20221130095015_add_spree_stripe_plan_model.rb +23 -0
- data/db/migrate/20221130100526_add_spree_stripe_customer.rb +11 -0
- data/db/migrate/20221130100903_add_spree_stripe_subscription_model.rb +23 -0
- data/db/migrate/20221130102650_add_spree_stripe_subscription_events_model.rb +16 -0
- data/db/migrate/20221202111450_add_weightage_to_stripe_plan.rb +10 -0
- data/db/migrate/20221209065244_add_schedules_to_subscription.rb +6 -0
- data/db/migrate/20221209094013_add_stripe_invoices.rb +33 -0
- data/db/migrate/20221209131817_add_user_to_invoice.rb +5 -0
- data/db/migrate/20221221080141_add_extra_plan_fields.rb +8 -0
- data/lib/generators/spree_stripe_subscriptions/install/install_generator.rb +20 -0
- data/lib/spree_stripe_subscriptions/engine.rb +24 -0
- data/lib/spree_stripe_subscriptions/factories.rb +6 -0
- data/lib/spree_stripe_subscriptions/version.rb +11 -0
- data/lib/spree_stripe_subscriptions.rb +4 -0
- data/spree_stripe_subscriptions.gemspec +34 -0
- metadata +207 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9085cdba71a500882f7360cb06f86ef9d9ee5617c52440e6c02c57e8a04218d6
|
4
|
+
data.tar.gz: d4891c7b4184367479b884b3cb740273b62d5fbcb1a18de4e3eb5ebeb81953db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 693ad9f3a7739fdd12d52410ec29ecc0036106cea56e4ee2edef986ff53e64ec5ddfc40184c03f9945eabe15c112f0aa5e2ddd252a2f351f2fe2861eaecfbecd
|
7
|
+
data.tar.gz: 5f4d4c7ba7b99676bd735834178a6aaf4f28f7793d13eaa465d2dd9fe3b7daa15b8fa5ae158fdd3659f1c1917e9fe4345baaef8948f74c7d9e02033b4969c91a
|
data/.gitignore
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
*.rbc
|
2
|
+
capybara-*.html
|
3
|
+
.rspec
|
4
|
+
/db/*.sqlite3
|
5
|
+
/db/*.sqlite3-journal
|
6
|
+
/db/*.sqlite3-[0-9]*
|
7
|
+
/public/system
|
8
|
+
/coverage/
|
9
|
+
/spec/tmp
|
10
|
+
*.orig
|
11
|
+
rerun.txt
|
12
|
+
pickle-email-*.html
|
13
|
+
|
14
|
+
# Ignore all logfiles and tempfiles.
|
15
|
+
/log/*
|
16
|
+
/tmp/*
|
17
|
+
!/log/.keep
|
18
|
+
!/tmp/.keep
|
19
|
+
|
20
|
+
# TODO Comment out this rule if you are OK with secrets being uploaded to the repo
|
21
|
+
config/initializers/secret_token.rb
|
22
|
+
config/master.key
|
23
|
+
|
24
|
+
# Only include if you have production secrets in this file, which is no longer a Rails default
|
25
|
+
# config/secrets.yml
|
26
|
+
|
27
|
+
# dotenv
|
28
|
+
# TODO Comment out this rule if environment variables can be committed
|
29
|
+
.env
|
30
|
+
|
31
|
+
## Environment normalization:
|
32
|
+
/.bundle
|
33
|
+
/vendor/bundle
|
34
|
+
|
35
|
+
# these should all be checked in to normalize the environment:
|
36
|
+
# Gemfile.lock, .ruby-version, .ruby-gemset
|
37
|
+
|
38
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
39
|
+
.rvmrc
|
40
|
+
|
41
|
+
# if using bower-rails ignore default bower_components path bower.json files
|
42
|
+
/vendor/assets/bower_components
|
43
|
+
*.bowerrc
|
44
|
+
bower.json
|
45
|
+
|
46
|
+
# Ignore pow environment settings
|
47
|
+
.powenv
|
48
|
+
|
49
|
+
# Ignore Byebug command history file.
|
50
|
+
.byebug_history
|
51
|
+
|
52
|
+
# Ignore node_modules
|
53
|
+
node_modules/
|
54
|
+
|
55
|
+
# Ignore precompiled javascript packs
|
56
|
+
/public/packs
|
57
|
+
/public/packs-test
|
58
|
+
/public/assets
|
59
|
+
|
60
|
+
# Ignore yarn files
|
61
|
+
/yarn-error.log
|
62
|
+
yarn-debug.log*
|
63
|
+
.yarn-integrity
|
64
|
+
|
65
|
+
# Ignore uploaded files in development
|
66
|
+
/storage/*
|
67
|
+
!/storage/.keep
|
68
|
+
|
69
|
+
# From Spree Extension
|
70
|
+
\#*
|
71
|
+
*~
|
72
|
+
.#*
|
73
|
+
.DS_Store
|
74
|
+
.idea
|
75
|
+
.localeapp/locales
|
76
|
+
.project
|
77
|
+
.vscode
|
78
|
+
coverage
|
79
|
+
default
|
80
|
+
Gemfile.lock
|
81
|
+
tmp
|
82
|
+
nbproject
|
83
|
+
pkg
|
84
|
+
*.sw?
|
85
|
+
spec/dummy
|
86
|
+
.sass-cache
|
87
|
+
public/spree
|
88
|
+
.ruby-version
|
89
|
+
.ruby-gemset
|
90
|
+
*.gem
|
91
|
+
*/*.gem
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require: rubocop-rails
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
DisplayCopNames: true
|
5
|
+
TargetRubyVersion: 2.5
|
6
|
+
Include:
|
7
|
+
- '**/Gemfile'
|
8
|
+
- '**/Rakefile'
|
9
|
+
- '**/Appraisals'
|
10
|
+
Exclude:
|
11
|
+
- 'spec/dummy/**/*'
|
12
|
+
- 'lib/generators/**/*'
|
13
|
+
|
14
|
+
Rails:
|
15
|
+
Enabled: true
|
16
|
+
|
17
|
+
Layout/LineLength:
|
18
|
+
Max: 150
|
19
|
+
|
20
|
+
# DISABLED
|
21
|
+
|
22
|
+
Style/Documentation:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Style/FrozenStringLiteralComment:
|
26
|
+
Enabled: false
|
data/.travis.yml
ADDED
@@ -0,0 +1,37 @@
|
|
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
|
14
|
+
|
15
|
+
language: ruby
|
16
|
+
|
17
|
+
rvm:
|
18
|
+
- 2.7
|
19
|
+
- 3.0
|
20
|
+
|
21
|
+
env:
|
22
|
+
- DB=mysql
|
23
|
+
- DB=postgres
|
24
|
+
|
25
|
+
before_install:
|
26
|
+
- mysql -u root -e "GRANT ALL ON *.* TO 'travis'@'%';"
|
27
|
+
|
28
|
+
before_script:
|
29
|
+
- CHROME_MAIN_VERSION=`google-chrome-stable --version | sed -E 's/(^Google Chrome |\.[0-9]+ )//g'`
|
30
|
+
- CHROMEDRIVER_VERSION=`curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_MAIN_VERSION"`
|
31
|
+
- curl "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" -O
|
32
|
+
- unzip chromedriver_linux64.zip -d ~/bin
|
33
|
+
- nvm install 16
|
34
|
+
|
35
|
+
script:
|
36
|
+
- bundle exec rake test_app
|
37
|
+
- bundle exec rspec
|
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
git_source(:github) do |repo_name|
|
4
|
+
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/')
|
5
|
+
"https://github.com/#{repo_name}.git"
|
6
|
+
end
|
7
|
+
|
8
|
+
# gem 'spree', github: 'spree/spree', branch: 'main'
|
9
|
+
# gem 'spree_backend', github: 'spree/spree', branch: 'main'
|
10
|
+
gem 'rails-controller-testing'
|
11
|
+
|
12
|
+
gemspec
|
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2022 [name of plugin creator]
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name Spree nor the names of its contributors may be used to
|
13
|
+
endorse or promote products derived from this software without specific
|
14
|
+
prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# SpreeStripeSubscriptions
|
2
|
+
|
3
|
+
Spree extension to manage stripe subscriptions using Stripe Checkout Session.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
1. Add this extension to your Gemfile with this line:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'spree_stripe_subscriptions'
|
11
|
+
```
|
12
|
+
|
13
|
+
2. Install the gem using Bundler
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
3. Copy & run migrations
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
bundle exec rails g spree_stripe_subscriptions:install
|
23
|
+
```
|
24
|
+
|
25
|
+
4. Restart your server
|
26
|
+
|
27
|
+
If your server was running, restart it so that it can find the assets properly.
|
28
|
+
|
29
|
+
## Configurations
|
30
|
+
|
31
|
+
### Customise Stripe Plans URL
|
32
|
+
|
33
|
+
To use customise URls, you can set the following configurations:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# In initializers/spree_stripe_subscriptions.rb
|
37
|
+
SpreeStripeSubscriptions::Config.stripe_plans_path = 'pricing' # Default url is 'stripe_plans'
|
38
|
+
SpreeStripeSubscriptions::Config.stripe_webhooks_path = 'webhook' # Default url is 'stripe_webhooks'
|
39
|
+
```
|
40
|
+
|
41
|
+
## Testing
|
42
|
+
|
43
|
+
First bundle your dependencies, then run `rake`. `rake` will default to building the dummy app if it does not exist, then it will run specs. The dummy app can be regenerated by using `rake test_app`.
|
44
|
+
|
45
|
+
```shell
|
46
|
+
bundle update
|
47
|
+
bundle exec rake
|
48
|
+
```
|
49
|
+
|
50
|
+
When testing your applications integration with this extension you may use it's factories.
|
51
|
+
Simply add this require statement to your spec_helper:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'spree_stripe_subscriptions/factories'
|
55
|
+
```
|
56
|
+
|
57
|
+
## Releasing
|
58
|
+
|
59
|
+
```shell
|
60
|
+
bundle exec gem bump -p -t
|
61
|
+
bundle exec gem release
|
62
|
+
```
|
63
|
+
|
64
|
+
For more options please see [gem-release REAMDE](https://github.com/svenfuchs/gem-release)
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
If you'd like to contribute, please take a look at the
|
69
|
+
[instructions](CONTRIBUTING.md) for installing dependencies and crafting a good
|
70
|
+
pull request.
|
71
|
+
|
72
|
+
Copyright (c) 2022 [name of extension creator], released under the New BSD License
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'spree/testing_support/extension_rake'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new
|
8
|
+
|
9
|
+
task :default do
|
10
|
+
if Dir['spec/dummy'].empty?
|
11
|
+
Rake::Task[:test_app].invoke
|
12
|
+
Dir.chdir('../../')
|
13
|
+
end
|
14
|
+
Rake::Task[:spec].invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generates a dummy app for testing'
|
18
|
+
task :test_app do
|
19
|
+
ENV['LIB_NAME'] = 'spree_stripe_subscriptions'
|
20
|
+
Rake::Task['extension:test_app'].invoke
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Spree
|
2
|
+
module Admin
|
3
|
+
class StripePlansController < Spree::Admin::ResourceController
|
4
|
+
before_action :load_strip_configuration
|
5
|
+
|
6
|
+
def new
|
7
|
+
@stripe_plan = @stripe_configuration.stripe_plans.build
|
8
|
+
end
|
9
|
+
|
10
|
+
def collection
|
11
|
+
super.reorder(:weightage)
|
12
|
+
end
|
13
|
+
|
14
|
+
def collection_url
|
15
|
+
spree.admin_stripe_configuration_stripe_plans_url(@stripe_configuration)
|
16
|
+
end
|
17
|
+
|
18
|
+
def new_object_url
|
19
|
+
spree.new_admin_stripe_configuration_stripe_plan_url(@stripe_configuration)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def stripe_configuration
|
25
|
+
@stripe_configuration ||= Spree::StripeConfiguration.where(id: params[:stripe_configuration_id]).first
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_strip_configuration
|
29
|
+
return if stripe_configuration
|
30
|
+
|
31
|
+
flash[:error] = I18n.t('spree_stripe_subscriptions.errors.stripe_configuration_doesnt_exist')
|
32
|
+
redirect_to admin_stripe_configurations_path
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spree
|
2
|
+
class StripePlansController < StoreController
|
3
|
+
before_action :load_active_plans, only: :index
|
4
|
+
before_action :load_user_subscriptions, only: :index
|
5
|
+
|
6
|
+
def index; end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def load_active_plans
|
11
|
+
@stripe_plans = Spree::StripePlan.active.order(:weightage)
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_user_subscriptions
|
15
|
+
@active_subscription = if spree_current_user.present?
|
16
|
+
spree_current_user.stripe_subscriptions.active.last
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Spree
|
2
|
+
class StripeSubscriptionsController < StoreController
|
3
|
+
before_action :login_required
|
4
|
+
before_action :load_stripe_plan
|
5
|
+
before_action :load_stripe_configuration
|
6
|
+
before_action :ensure_stripe_plan_exist
|
7
|
+
before_action :ensure_stripe_customer_exist
|
8
|
+
before_action :load_stripe_subscription, only: [:update, :destroy, :change_payment_details, :update_payment_details]
|
9
|
+
before_action :ensure_active_subscription, only: :downgrade
|
10
|
+
|
11
|
+
def create
|
12
|
+
checkout_session = Stripe::Checkout::Session.create(
|
13
|
+
mode: 'subscription',
|
14
|
+
customer: @stripe_customer.id,
|
15
|
+
customer_update: { address: 'auto', name: 'auto' },
|
16
|
+
client_reference_id: spree_current_user.id,
|
17
|
+
line_items: [
|
18
|
+
{
|
19
|
+
'price': @stripe_plan.id,
|
20
|
+
'quantity': 1
|
21
|
+
}
|
22
|
+
],
|
23
|
+
automatic_tax: { enabled: @configuration.automatic_tax },
|
24
|
+
tax_id_collection: { enabled: @configuration.tax_id_collection },
|
25
|
+
billing_address_collection: @configuration.billing_address_collection,
|
26
|
+
success_url: stripe_plans_url,
|
27
|
+
cancel_url: stripe_plans_url
|
28
|
+
)
|
29
|
+
redirect_to checkout_session.url
|
30
|
+
rescue StandardError => e
|
31
|
+
Rails.logger.error e.message
|
32
|
+
flash[:error] = e.message
|
33
|
+
redirect_to stripe_plans_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def update
|
37
|
+
@subscription.cancel_renewal
|
38
|
+
flash[:success] = I18n.t('spree_stripe_subscriptions.messages.success.successfully_canceled_renewal')
|
39
|
+
redirect_to stripe_plans_path
|
40
|
+
end
|
41
|
+
|
42
|
+
def change_payment_details
|
43
|
+
checkout_session = Stripe::Checkout::Session.create(
|
44
|
+
payment_method_types: ['card'],
|
45
|
+
mode: 'setup',
|
46
|
+
customer: @stripe_customer.id,
|
47
|
+
setup_intent_data: {
|
48
|
+
metadata: {
|
49
|
+
subscription_id: @subscription.stripe_subscription_id
|
50
|
+
}
|
51
|
+
},
|
52
|
+
success_url: "#{update_payment_details_stripe_plan_stripe_subscription_url(@plan, @subscription)}?session_id={CHECKOUT_SESSION_ID}",
|
53
|
+
cancel_url: stripe_plans_url
|
54
|
+
)
|
55
|
+
redirect_to checkout_session.url
|
56
|
+
rescue StandardError => e
|
57
|
+
Rails.logger.error e.message
|
58
|
+
flash[:error] = e.message
|
59
|
+
redirect_to stripe_plans_path
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_payment_details
|
63
|
+
return unless (session_id = params[:session_id])
|
64
|
+
|
65
|
+
checkout_session = Stripe::Checkout::Session.retrieve(session_id)
|
66
|
+
setup_intent = Stripe::SetupIntent.retrieve(checkout_session.setup_intent)
|
67
|
+
|
68
|
+
# Set invoice_settings.default_payment_method on the Customer
|
69
|
+
Stripe::Customer.update(
|
70
|
+
@stripe_customer.id,
|
71
|
+
{ invoice_settings: { default_payment_method: setup_intent.payment_method } }
|
72
|
+
)
|
73
|
+
|
74
|
+
# Set default_payment_method on the Subscription
|
75
|
+
Stripe::Subscription.update(
|
76
|
+
@subscription.stripe_subscription_id,
|
77
|
+
{
|
78
|
+
default_payment_method: setup_intent.payment_method
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
82
|
+
flash[:success] = I18n.t('spree_stripe_subscriptions.messages.success.successfully_updated')
|
83
|
+
redirect_to stripe_plans_path
|
84
|
+
rescue StandardError => e
|
85
|
+
Rails.logger.error e.message
|
86
|
+
flash[:error] = e.message
|
87
|
+
redirect_to stripe_plans_path
|
88
|
+
end
|
89
|
+
|
90
|
+
def downgrade
|
91
|
+
active_subscription = spree_current_user.stripe_subscriptions.active.find(params[:id])
|
92
|
+
destination_plan = Spree::StripePlan.active.find(params[:stripe_plan_id])
|
93
|
+
|
94
|
+
subscription_schedule = active_subscription.stripe_subscription_schedule
|
95
|
+
|
96
|
+
if subscription_schedule.present?
|
97
|
+
Stripe::SubscriptionSchedule.update(
|
98
|
+
subscription_schedule.id,
|
99
|
+
{
|
100
|
+
phases: [
|
101
|
+
{
|
102
|
+
items: [
|
103
|
+
{ price: active_subscription.plan.stripe_plan_id, quantity: 1 }
|
104
|
+
],
|
105
|
+
start_date: active_subscription.current_period_start.to_i,
|
106
|
+
end_date: active_subscription.current_period_end.to_i
|
107
|
+
},
|
108
|
+
{
|
109
|
+
items: [
|
110
|
+
{ price: destination_plan.stripe_plan_id, quantity: 1 }
|
111
|
+
],
|
112
|
+
start_date: active_subscription.current_period_end.to_i,
|
113
|
+
# end_date: active_subscription.current_period_end.to_i + 1.month.to_i
|
114
|
+
}
|
115
|
+
]
|
116
|
+
}
|
117
|
+
)
|
118
|
+
flash[:success] = I18n.t('spree_stripe_subscriptions.messages.success.successfully_downgraded')
|
119
|
+
redirect_to stripe_plans_path
|
120
|
+
else
|
121
|
+
flash[:alert] = I18n.t('spree_stripe_subscriptions.messages.errors.cannot_process_request')
|
122
|
+
redirect_to stripe_plans_path
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def destroy
|
127
|
+
@subscription.unsubscribe
|
128
|
+
flash[:success] = I18n.t('spree_stripe_subscriptions.messages.success.successfully_unsubscribed')
|
129
|
+
redirect_to stripe_plans_path
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def login_required
|
135
|
+
raise CanCan::AccessDenied if spree_current_user.blank?
|
136
|
+
end
|
137
|
+
|
138
|
+
def load_stripe_plan
|
139
|
+
@plan = Spree::StripePlan.active.find(params[:stripe_plan_id])
|
140
|
+
end
|
141
|
+
|
142
|
+
def load_stripe_configuration
|
143
|
+
@configuration = @plan.configuration
|
144
|
+
end
|
145
|
+
|
146
|
+
def ensure_active_subscription
|
147
|
+
return if spree_current_user.stripe_subscriptions.active.exists?
|
148
|
+
|
149
|
+
flash[:alert] = I18n.t('spree_stripe_subscriptions.messages.errors.no_active_subscription')
|
150
|
+
redirect_to stripe_plans_path
|
151
|
+
end
|
152
|
+
|
153
|
+
def ensure_stripe_customer_exist
|
154
|
+
@stripe_customer = spree_current_user.find_or_create_stripe_customer
|
155
|
+
end
|
156
|
+
|
157
|
+
def ensure_stripe_plan_exist
|
158
|
+
@stripe_plan = @plan.find_or_create_stripe_plan
|
159
|
+
raise CanCan::AccessDenied if @stripe_plan.nil?
|
160
|
+
end
|
161
|
+
|
162
|
+
def load_stripe_subscription
|
163
|
+
@subscription = @plan.stripe_subscriptions.find(params[:id])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Ref: https://github.com/stripe-samples/checkout-foundations-ruby/blob/main/server.rb
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class StripeWebhooksController < BaseController
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
|
7
|
+
before_action :load_stripe_configuration
|
8
|
+
|
9
|
+
respond_to :json
|
10
|
+
|
11
|
+
def handler
|
12
|
+
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
|
13
|
+
payload = request.body.read
|
14
|
+
|
15
|
+
event = Stripe::Webhook.construct_event(
|
16
|
+
payload, sig_header, @stripe_configuration.preferred_webhook_secret
|
17
|
+
)
|
18
|
+
|
19
|
+
if event.present? && (%w[customer.subscription.updated customer.subscription.deleted].include? event.type)
|
20
|
+
subscription = Spree::StripeSubscription.create_or_update_subscription(event)
|
21
|
+
subscription.register_webhook_event(event)
|
22
|
+
elsif event.present? && (%w[subscription_schedule.updated].include? event.type)
|
23
|
+
subscription = Spree::StripeSubscription.update_subscription_schedule(event)
|
24
|
+
subscription.register_webhook_event(event) if subscription.present?
|
25
|
+
elsif event.present? && (%w[invoice.paid].include? event.type)
|
26
|
+
subscription = Spree::StripeSubscription.create_or_update_invoice(event)
|
27
|
+
subscription.register_webhook_event(event) if subscription.present?
|
28
|
+
else
|
29
|
+
Rails.logger.warn "Unhandled event type: #{event&.type}"
|
30
|
+
end
|
31
|
+
|
32
|
+
render json: { success: true }, status: :ok
|
33
|
+
rescue JSON::ParserError => e
|
34
|
+
# Invalid payload
|
35
|
+
Rails.logger.error e
|
36
|
+
render json: { success: false }, status: :not_found
|
37
|
+
rescue Stripe::SignatureVerificationError => e
|
38
|
+
# invalid signature
|
39
|
+
Rails.logger.error e
|
40
|
+
render json: { success: false }, status: :not_found
|
41
|
+
rescue ActiveRecord::RecordNotFound => e
|
42
|
+
# Either StripeCustomer or StripePlan doesn't exist in our records
|
43
|
+
Rails.logger.error e
|
44
|
+
render json: { success: false }, status: :ok
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def load_stripe_configuration
|
50
|
+
@stripe_configuration = Spree::StripeConfiguration.active.last
|
51
|
+
return if @stripe_configuration.present? && @stripe_configuration.preferred_keys_available?
|
52
|
+
|
53
|
+
render json: { success: false }, status: :not_found
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Spree
|
2
|
+
class StripeConfiguration < Spree::Base
|
3
|
+
acts_as_paranoid
|
4
|
+
|
5
|
+
preference :secret_key, :string
|
6
|
+
preference :public_key, :string
|
7
|
+
preference :webhook_secret, :string
|
8
|
+
|
9
|
+
ADDRESS_COLLECTION_OPTIONS = {
|
10
|
+
'auto' => 'auto',
|
11
|
+
'required' => 'required'
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
validates :billing_address_collection, inclusion: { in: ADDRESS_COLLECTION_OPTIONS.keys }
|
15
|
+
|
16
|
+
has_many :stripe_plans,
|
17
|
+
class_name: 'Spree::StripePlan',
|
18
|
+
foreign_key: :configuration_id,
|
19
|
+
inverse_of: :configuration,
|
20
|
+
dependent: :restrict_with_error
|
21
|
+
|
22
|
+
scope :active, -> { where(active: true) }
|
23
|
+
|
24
|
+
def preferred_keys_available?
|
25
|
+
preferred_secret_key.present? && preferred_public_key.present? && preferred_webhook_secret.present?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spree
|
2
|
+
class StripeCustomer < Spree::Base
|
3
|
+
acts_as_paranoid
|
4
|
+
|
5
|
+
belongs_to :user, class_name: Spree.user_class.to_s
|
6
|
+
|
7
|
+
has_many :stripe_subscriptions,
|
8
|
+
class_name: 'Spree::StripeSubscription',
|
9
|
+
foreign_key: :customer_id,
|
10
|
+
inverse_of: :customer,
|
11
|
+
dependent: :restrict_with_error
|
12
|
+
has_many :stripe_invoices,
|
13
|
+
class_name: 'Spree::StripeInvoice',
|
14
|
+
foreign_key: :customer_id,
|
15
|
+
inverse_of: :customer,
|
16
|
+
dependent: :restrict_with_error
|
17
|
+
|
18
|
+
def stripe_customer
|
19
|
+
stripe_customer = nil
|
20
|
+
return nil if deleted_at.present?
|
21
|
+
|
22
|
+
stripe_customer = Stripe::Customer.retrieve(stripe_customer_id)
|
23
|
+
rescue StandardError
|
24
|
+
destroy
|
25
|
+
stripe_customer = nil
|
26
|
+
ensure
|
27
|
+
stripe_customer
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|