spree_stripe_subscriptions 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +91 -0
  3. data/.rubocop.yml +26 -0
  4. data/.travis.yml +37 -0
  5. data/Gemfile +12 -0
  6. data/LICENSE +26 -0
  7. data/README.md +72 -0
  8. data/Rakefile +21 -0
  9. data/app/controllers/spree/admin/stripe_configurations_controller.rb +6 -0
  10. data/app/controllers/spree/admin/stripe_customers_controller.rb +6 -0
  11. data/app/controllers/spree/admin/stripe_plans_controller.rb +36 -0
  12. data/app/controllers/spree/admin/stripe_subscription_events_controller.rb +6 -0
  13. data/app/controllers/spree/admin/stripe_subscriptions_controller.rb +6 -0
  14. data/app/controllers/spree/stripe_plans_controller.rb +20 -0
  15. data/app/controllers/spree/stripe_subscriptions_controller.rb +166 -0
  16. data/app/controllers/spree/stripe_webhooks_controller.rb +56 -0
  17. data/app/models/spree/stripe_configuration.rb +28 -0
  18. data/app/models/spree/stripe_customer.rb +30 -0
  19. data/app/models/spree/stripe_invoice.rb +21 -0
  20. data/app/models/spree/stripe_plan.rb +104 -0
  21. data/app/models/spree/stripe_subscription.rb +167 -0
  22. data/app/models/spree/stripe_subscription_event.rb +10 -0
  23. data/app/models/spree_stripe_subscriptions/configuration.rb +15 -0
  24. data/app/models/spree_stripe_subscriptions/user_decorator.rb +50 -0
  25. data/app/overrides/spree/admin/shared/_main_menu.rb +13 -0
  26. data/app/serializers/.gitkeep +0 -0
  27. data/app/services/.gitkeep +0 -0
  28. data/app/views/spree/admin/shared/sub_menu/_stripe_subscriptions.html.erb +8 -0
  29. data/app/views/spree/admin/stripe_configurations/_form.html.erb +42 -0
  30. data/app/views/spree/admin/stripe_configurations/edit.html.erb +17 -0
  31. data/app/views/spree/admin/stripe_configurations/index.html.erb +44 -0
  32. data/app/views/spree/admin/stripe_configurations/new.html.erb +13 -0
  33. data/app/views/spree/admin/stripe_customers/index.html.erb +36 -0
  34. data/app/views/spree/admin/stripe_plans/_form.html.erb +50 -0
  35. data/app/views/spree/admin/stripe_plans/edit.html.erb +15 -0
  36. data/app/views/spree/admin/stripe_plans/index.html.erb +59 -0
  37. data/app/views/spree/admin/stripe_plans/new.html.erb +15 -0
  38. data/app/views/spree/admin/stripe_subscription_events/index.html.erb +36 -0
  39. data/app/views/spree/admin/stripe_subscriptions/index.html.erb +40 -0
  40. data/app/views/spree/stripe_plans/index.html.erb +77 -0
  41. data/bin/rails +8 -0
  42. data/config/initializers/stripe.rb +11 -0
  43. data/config/locales/en.yml +80 -0
  44. data/config/routes.rb +27 -0
  45. data/db/migrate/20221130094411_add_spree_stripe_configuration_model.rb +14 -0
  46. data/db/migrate/20221130095015_add_spree_stripe_plan_model.rb +23 -0
  47. data/db/migrate/20221130100526_add_spree_stripe_customer.rb +11 -0
  48. data/db/migrate/20221130100903_add_spree_stripe_subscription_model.rb +23 -0
  49. data/db/migrate/20221130102650_add_spree_stripe_subscription_events_model.rb +16 -0
  50. data/db/migrate/20221202111450_add_weightage_to_stripe_plan.rb +10 -0
  51. data/db/migrate/20221209065244_add_schedules_to_subscription.rb +6 -0
  52. data/db/migrate/20221209094013_add_stripe_invoices.rb +33 -0
  53. data/db/migrate/20221209131817_add_user_to_invoice.rb +5 -0
  54. data/db/migrate/20221221080141_add_extra_plan_fields.rb +8 -0
  55. data/lib/generators/spree_stripe_subscriptions/install/install_generator.rb +20 -0
  56. data/lib/spree_stripe_subscriptions/engine.rb +24 -0
  57. data/lib/spree_stripe_subscriptions/factories.rb +6 -0
  58. data/lib/spree_stripe_subscriptions/version.rb +11 -0
  59. data/lib/spree_stripe_subscriptions.rb +4 -0
  60. data/spree_stripe_subscriptions.gemspec +34 -0
  61. 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,6 @@
1
+ module Spree
2
+ module Admin
3
+ class StripeConfigurationsController < Spree::Admin::ResourceController
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Admin
3
+ class StripeCustomersController < Spree::Admin::ResourceController
4
+ end
5
+ end
6
+ 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,6 @@
1
+ module Spree
2
+ module Admin
3
+ class StripeSubscriptionEventsController < Spree::Admin::ResourceController
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Admin
3
+ class StripeSubscriptionsController < Spree::Admin::ResourceController
4
+ end
5
+ end
6
+ 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