spree_account_recurring 1.0.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.
- data/LICENSE +26 -0
- data/README.md +108 -0
- data/app/assets/javascripts/admin/spree_account_recurring.js +1 -0
- data/app/assets/javascripts/store/spree_account_recurring.js +1 -0
- data/app/assets/javascripts/store/stripe.js +53 -0
- data/app/assets/stylesheets/admin/spree_account_recurring.css +3 -0
- data/app/assets/stylesheets/store/spree_account_recurring.css +3 -0
- data/app/controllers/concerns/spree/admin/ransack_date_search.rb +47 -0
- data/app/controllers/spree/admin/plans_controller.rb +63 -0
- data/app/controllers/spree/admin/recurrings_controller.rb +66 -0
- data/app/controllers/spree/admin/subscription_events_controller.rb +14 -0
- data/app/controllers/spree/admin/subscriptions_controller.rb +14 -0
- data/app/controllers/spree/plans_controller.rb +7 -0
- data/app/controllers/spree/recurring_hooks_controller.rb +53 -0
- data/app/controllers/spree/subscriptions_controller.rb +69 -0
- data/app/models/concerns/before_each.rb +21 -0
- data/app/models/concerns/restrictive_destroyer.rb +44 -0
- data/app/models/concerns/spree/plan/api_handler.rb +46 -0
- data/app/models/concerns/spree/recurring/stripe_recurring/api_handler.rb +28 -0
- data/app/models/concerns/spree/recurring/stripe_recurring/api_handler/plan_api_handler.rb +49 -0
- data/app/models/concerns/spree/recurring/stripe_recurring/api_handler/subscription_api_handler.rb +20 -0
- data/app/models/concerns/spree/recurring/stripe_recurring/api_handler/subscription_event_api_handler.rb +19 -0
- data/app/models/concerns/spree/subscription/api_handler.rb +38 -0
- data/app/models/concerns/spree/subscription/role_subscriber.rb +36 -0
- data/app/models/spree/plan.rb +31 -0
- data/app/models/spree/recurring.rb +27 -0
- data/app/models/spree/recurring/stripe_recurring.rb +16 -0
- data/app/models/spree/subscriber_ability.rb +17 -0
- data/app/models/spree/subscription.rb +33 -0
- data/app/models/spree/subscription_event.rb +11 -0
- data/app/models/spree/user_decorator.rb +14 -0
- data/app/overrides/admin/view_decorator.rb +27 -0
- data/app/views/spree/admin/plans/_form.html.erb +47 -0
- data/app/views/spree/admin/plans/destroy.js.erb +0 -0
- data/app/views/spree/admin/plans/edit.html.erb +22 -0
- data/app/views/spree/admin/plans/index.html.erb +70 -0
- data/app/views/spree/admin/plans/new.html.erb +22 -0
- data/app/views/spree/admin/recurrings/_form.html.erb +42 -0
- data/app/views/spree/admin/recurrings/destroy.js.erb +0 -0
- data/app/views/spree/admin/recurrings/edit.html.erb +23 -0
- data/app/views/spree/admin/recurrings/index.html.erb +48 -0
- data/app/views/spree/admin/recurrings/new.html.erb +22 -0
- data/app/views/spree/admin/subscription_events/index.html.erb +72 -0
- data/app/views/spree/admin/subscriptions/index.html.erb +76 -0
- data/app/views/spree/plans/index.html.erb +14 -0
- data/app/views/spree/subscriptions/new.html.erb +36 -0
- data/app/views/spree/subscriptions/show.html.erb +4 -0
- data/config/initializers/stripe.rb +2 -0
- data/config/locales/en.yml +43 -0
- data/config/routes.rb +24 -0
- data/db/migrate/20131119054112_create_spree_recurring.rb +13 -0
- data/db/migrate/20131119054212_create_spree_plan.rb +21 -0
- data/db/migrate/20131119054322_create_spree_subscription.rb +16 -0
- data/db/migrate/20131125123820_create_spree_subscription_events.rb +14 -0
- data/db/migrate/20131202110012_add_subscriber_role_to_spree_roles.rb +15 -0
- data/db/migrate/20140301141327_add_stripe_customer_id_to_spree_users.rb +6 -0
- data/db/migrate/20140303072857_add_default_to_spree_plans.rb +6 -0
- data/db/migrate/20140319092254_add_response_to_spree_subscription_events.rb +5 -0
- data/lib/generators/spree_account_recurring/install/install_generator.rb +31 -0
- data/lib/spree_account_recurring.rb +2 -0
- data/lib/spree_account_recurring/engine.rb +22 -0
- data/lib/spree_account_recurring/factories.rb +6 -0
- data/lib/spree_account_recurring/testing_support/ransack_date_search_helper.rb +125 -0
- metadata +188 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
module Spree
|
2
|
+
class SubscriptionsController < StoreController
|
3
|
+
prepend_before_filter :load_object
|
4
|
+
before_action :find_active_plan, only: [:new, :create]
|
5
|
+
before_action :find_plan, only: [:show, :destroy]
|
6
|
+
before_action :find_subscription, only: [:show, :destroy]
|
7
|
+
before_action :authenticate_subscription, only: [:new, :create]
|
8
|
+
|
9
|
+
def new
|
10
|
+
@subscription = @plan.subscriptions.build
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
@subscription = @plan.subscriptions.build(subscription_params.merge(user_id: spree_current_user.id))
|
15
|
+
if @subscription.save_and_manage_api
|
16
|
+
redirect_to recurring_plan_subscription_url(@plan, @subscription), notice: "Thank you for subscribing!"
|
17
|
+
else
|
18
|
+
render :new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy
|
23
|
+
if @subscription.save_and_manage_api(unsubscribed_at: Time.current)
|
24
|
+
redirect_to plans_path, notice: "Subscription has been cancelled."
|
25
|
+
else
|
26
|
+
render :show
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def find_active_plan
|
33
|
+
unless @plan = Spree::Plan.active.where(id: params[:plan_id]).first
|
34
|
+
flash[:error] = "Plan not found."
|
35
|
+
redirect_to plans_url
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_plan
|
40
|
+
unless @plan = Spree::Plan.where(id: params[:plan_id]).first
|
41
|
+
flash[:error] = "Plan not found."
|
42
|
+
redirect_to plans_url
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_subscription
|
47
|
+
unless @subscription = @plan.subscriptions.undeleted.where(id: params[:id]).first
|
48
|
+
flash[:error] = "Subscription not found."
|
49
|
+
redirect_to root_url
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def subscription_params
|
54
|
+
params.require(:subscription).permit(:email, :card_token)
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_object
|
58
|
+
@user ||= spree_current_user
|
59
|
+
authorize! params[:action].to_sym, @user
|
60
|
+
end
|
61
|
+
|
62
|
+
def authenticate_subscription
|
63
|
+
if subscription = spree_current_user.subscriptions.undeleted.first
|
64
|
+
flash[:alert] = "You have already subscribed."
|
65
|
+
redirect_to recurring_plan_subscription_url(@plan, subscription)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module BeforeEach
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def method_added(method)
|
6
|
+
method = method.to_s.gsub(/_with(out)?_before$/, '')
|
7
|
+
with_method, without_method = "#{method}_with_before", "#{method}_without_before"
|
8
|
+
|
9
|
+
return if method == 'before_each' or method_defined?(with_method)
|
10
|
+
|
11
|
+
define_method(with_method) do |*args, &block|
|
12
|
+
before_each
|
13
|
+
send(without_method, *args, &block)
|
14
|
+
end
|
15
|
+
alias_method_chain(method, :before)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def before_each
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RestrictiveDestroyer
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_destroy :destroyable?
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def acts_as_restrictive_destroyer(options={})
|
10
|
+
raise ArgumentError, "Hash expected, got #{options.class.name}" unless options.is_a?(Hash)
|
11
|
+
class_attribute :restrictive_destroyer_config, :restrictive_destroyer_col_ref
|
12
|
+
self.restrictive_destroyer_config = { column: "deleted_at" }.merge!(options)
|
13
|
+
self.restrictive_destroyer_col_ref = "#{self.table_name}.#{restrictive_destroyer_config[:column]}"
|
14
|
+
scope :undeleted, -> { where("#{restrictive_destroyer_col_ref} IS NULL") }
|
15
|
+
scope :deleted, -> { where("#{restrictive_destroyer_col_ref} IS NOT NULL") }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def readonly?
|
20
|
+
is_destroyed?
|
21
|
+
end
|
22
|
+
|
23
|
+
def restrictive_destroy
|
24
|
+
update_attributes("#{restrictive_destroyer_attr_ref}" => Time.current)
|
25
|
+
end
|
26
|
+
|
27
|
+
def restrictive_destroy_with_api
|
28
|
+
save_and_manage_api("#{restrictive_destroyer_attr_ref}" => Time.current)
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_destroyed?
|
32
|
+
!send("#{restrictive_destroyer_attr_ref}_changed?") && send("#{restrictive_destroyer_attr_ref}?")
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def restrictive_destroyer_attr_ref
|
38
|
+
self.class.restrictive_destroyer_config[:column]
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroyable?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Spree
|
2
|
+
class Plan < ActiveRecord::Base
|
3
|
+
module ApiHandler
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before_validation :set_api_plan_id, on: :create
|
8
|
+
after_create :create_plan
|
9
|
+
after_update :delete_plan, :if => [:deleted_at_changed?, :deleted_at?]
|
10
|
+
after_update :update_plan, :if => :name_changed?
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_plan
|
14
|
+
provider.create_plan(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete_plan
|
18
|
+
provider.delete_plan(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_plan
|
22
|
+
provider.update_plan(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def save_and_manage_api(*args)
|
26
|
+
begin
|
27
|
+
new_record? ? save : update_attributes(*args)
|
28
|
+
rescue provider.error_class, ActiveRecord::RecordNotFound => e
|
29
|
+
logger.error "Error: #{e.message}"
|
30
|
+
errors.add :base, e.message
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def provider
|
36
|
+
recurring.present? ? recurring : (raise ActiveRecord::RecordNotFound.new("Provider not found."))
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def set_api_plan_id
|
42
|
+
provider.set_api_plan_id(self)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Spree
|
2
|
+
class Recurring < ActiveRecord::Base
|
3
|
+
class StripeRecurring < Spree::Recurring
|
4
|
+
module ApiHandler
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include BeforeEach
|
9
|
+
include PlanApiHandler
|
10
|
+
include SubscriptionApiHandler
|
11
|
+
include SubscriptionEventApiHandler
|
12
|
+
end
|
13
|
+
|
14
|
+
def error_class
|
15
|
+
Stripe::InvalidRequestError
|
16
|
+
end
|
17
|
+
|
18
|
+
def raise_invalid_object_error(object, type)
|
19
|
+
raise error_class.new("Not a valid object.") unless object.is_a?(type)
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_api_key
|
23
|
+
Stripe.api_key = preferred_secret_key
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Spree
|
2
|
+
class Recurring < ActiveRecord::Base
|
3
|
+
class StripeRecurring < Spree::Recurring
|
4
|
+
module ApiHandler
|
5
|
+
module PlanApiHandler
|
6
|
+
def create_plan(plan)
|
7
|
+
raise_invalid_object_error(plan, Spree::Plan)
|
8
|
+
Stripe::Plan.create(
|
9
|
+
amount: stripe_amount(plan.amount),
|
10
|
+
interval: plan.interval,
|
11
|
+
interval_count: plan.interval_count,
|
12
|
+
name: plan.name,
|
13
|
+
currency: plan.currency,
|
14
|
+
id: plan.api_plan_id,
|
15
|
+
trial_period_days: plan.trial_period_days
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete_plan(plan)
|
20
|
+
raise_invalid_object_error(plan, Spree::Plan)
|
21
|
+
stripe_plan = retrieve_api_plan(plan)
|
22
|
+
stripe_plan.delete
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_plan(plan)
|
26
|
+
raise_invalid_object_error(plan, Spree::Plan)
|
27
|
+
stripe_plan = retrieve_api_plan(plan)
|
28
|
+
stripe_plan.name = plan.name
|
29
|
+
stripe_plan.save
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_api_plan_id(plan)
|
33
|
+
plan.api_plan_id = "KS-Plan-#{Time.current.to_i}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def retrieve_api_plan(plan)
|
39
|
+
Stripe::Plan.retrieve(plan.api_plan_id)
|
40
|
+
end
|
41
|
+
|
42
|
+
def stripe_amount(amount)
|
43
|
+
amount * 100
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/app/models/concerns/spree/recurring/stripe_recurring/api_handler/subscription_api_handler.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spree
|
2
|
+
class Recurring < ActiveRecord::Base
|
3
|
+
class StripeRecurring < Spree::Recurring
|
4
|
+
module ApiHandler
|
5
|
+
module SubscriptionApiHandler
|
6
|
+
def subscribe(subscription)
|
7
|
+
raise_invalid_object_error(subscription, Spree::Subscription)
|
8
|
+
customer = subscription.user.find_or_create_stripe_customer(subscription.card_token)
|
9
|
+
customer.subscriptions.create(plan: subscription.api_plan_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def unsubscribe(subscription)
|
13
|
+
raise_invalid_object_error(subscription, Spree::Subscription)
|
14
|
+
subscription.user.api_customer.cancel_subscription
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Spree
|
2
|
+
class Recurring < ActiveRecord::Base
|
3
|
+
class StripeRecurring < Spree::Recurring
|
4
|
+
module ApiHandler
|
5
|
+
module SubscriptionEventApiHandler
|
6
|
+
def retrieve_event(event_id)
|
7
|
+
begin
|
8
|
+
Stripe::Event.retrieve(event_id)
|
9
|
+
rescue error_class => e
|
10
|
+
logger.error "Stripe Event error: #{e.message}"
|
11
|
+
errors.add :base, "Stripe Event error: #{e.message}"
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Spree
|
2
|
+
class Subscription < ActiveRecord::Base
|
3
|
+
module ApiHandler
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
attr_accessor :card_token
|
8
|
+
before_create :subscribe
|
9
|
+
before_update :unsubscribe, :if => [:unsubscribed_at_changed?, :unsubscribed_at?]
|
10
|
+
end
|
11
|
+
|
12
|
+
def subscribe
|
13
|
+
provider.subscribe(self)
|
14
|
+
self.subscribed_at = Time.current
|
15
|
+
end
|
16
|
+
|
17
|
+
def unsubscribe
|
18
|
+
provider.unsubscribe(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def save_and_manage_api(*args)
|
22
|
+
begin
|
23
|
+
new_record? ? save : update_attributes(*args)
|
24
|
+
rescue provider.error_class, ActiveRecord::RecordNotFound => e
|
25
|
+
logger.error "Error while subscribing: #{e.message}"
|
26
|
+
errors.add :base, "There was a problem with your credit card"
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def provider
|
32
|
+
plan.try(:recurring).present? ? plan.recurring : (raise ActiveRecord::RecordNotFound.new("Provider not found."))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Spree
|
2
|
+
class Subscription < ActiveRecord::Base
|
3
|
+
module RoleSubscriber
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before_create :add_role_subscriber, :unless => :unsubscribed_at?
|
8
|
+
before_update :remove_role_subscriber, :if => :unsubscribed_at?
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_role_subscriber
|
12
|
+
if role = subscriber_role
|
13
|
+
user.spree_roles << role
|
14
|
+
else
|
15
|
+
errors.add :base, "Subscriber role is not present."
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_role_subscriber
|
21
|
+
if role = subscriber_role
|
22
|
+
user.spree_roles.destroy(role)
|
23
|
+
else
|
24
|
+
errors.add :base, "Subscriber role is not present."
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def subscriber_role
|
32
|
+
Spree::Role.where(name: 'subscriber').first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Spree
|
2
|
+
class Plan < ActiveRecord::Base
|
3
|
+
include RestrictiveDestroyer
|
4
|
+
include ApiHandler
|
5
|
+
|
6
|
+
acts_as_restrictive_destroyer
|
7
|
+
|
8
|
+
belongs_to :recurring
|
9
|
+
has_many :subscriptions
|
10
|
+
|
11
|
+
validates :amount, :interval, :interval_count, :name, :currency, :recurring_id, :api_plan_id, presence: true
|
12
|
+
attr_readonly :amount, :interval, :currency, :id, :trial_period_days, :interval_count, :recurring_id, :api_plan_id
|
13
|
+
|
14
|
+
before_validation :manage_default, if: :default_changed?
|
15
|
+
|
16
|
+
scope :active, -> { undeleted.where(active: true) }
|
17
|
+
scope :visible, -> { active.joins(:recurring).where(["spree_recurrings.active = ? AND spree_recurrings.deleted_at IS NULL", true]) }
|
18
|
+
|
19
|
+
def visible?
|
20
|
+
active? && !is_destroyed? && recurring.visible?
|
21
|
+
end
|
22
|
+
|
23
|
+
def manage_default
|
24
|
+
recurring.plans.undeleted.where.not(id: id).update_all(default: false)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.default
|
28
|
+
visible.find_by(default: true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Spree
|
2
|
+
class Recurring < ActiveRecord::Base
|
3
|
+
include RestrictiveDestroyer
|
4
|
+
|
5
|
+
acts_as_restrictive_destroyer
|
6
|
+
|
7
|
+
preference :secret_key, :string
|
8
|
+
preference :public_key, :string
|
9
|
+
|
10
|
+
has_many :plans
|
11
|
+
attr_readonly :type
|
12
|
+
validates :type, :name, presence: true
|
13
|
+
scope :active, -> { undeleted.where(active: true) }
|
14
|
+
|
15
|
+
def self.display_name
|
16
|
+
name.gsub(%r{.+:}, '')
|
17
|
+
end
|
18
|
+
|
19
|
+
def visible?
|
20
|
+
active? && !is_destroyed?
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_plan
|
24
|
+
plans.default
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|