-
-
Notifications
You must be signed in to change notification settings - Fork 353
Expand file tree
/
Copy pathsubscription.rb
More file actions
135 lines (108 loc) · 4.2 KB
/
Copy pathsubscription.rb
File metadata and controls
135 lines (108 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
module Pay
class Subscription < Pay::ApplicationRecord
STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid paused]
# Associations
belongs_to :customer
belongs_to :payment_method, optional: true, primary_key: :processor_id
has_many :charges
# Scopes
scope :for_name, ->(name) { where(name: name) }
scope :on_trial, -> { where(status: ["on_trial", "trialing", "active"]).where("trial_ends_at > ?", Time.current) }
scope :canceled, -> { where.not(ends_at: nil) }
scope :cancelled, -> { canceled }
scope :on_grace_period, -> { where("#{table_name}.ends_at IS NOT NULL AND #{table_name}.ends_at > ?", Time.current) }
scope :active, -> { where(status: "active").pause_not_started.where("#{table_name}.ends_at IS NULL OR #{table_name}.ends_at > ?", Time.current).or(on_trial) }
scope :paused, -> { where(status: "paused").or(where("pause_starts_at <= ?", Time.current)) }
scope :pause_not_started, -> { where("pause_starts_at IS NULL OR pause_starts_at > ?", Time.current) }
scope :active_or_paused, -> { active.or(paused) }
scope :incomplete, -> { where(status: :incomplete) }
scope :past_due, -> { where(status: :past_due) }
scope :unpaid, -> { where(status: :unpaid) }
scope :metered, -> { where(metered: true) }
scope :with_active_customer, -> { joins(:customer).merge(Customer.active) }
scope :with_deleted_customer, -> { joins(:customer).merge(Customer.deleted) }
# Callbacks
before_destroy :cancel_if_active
# Validations
validates :name, presence: true
validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
validates :processor_plan, presence: true
validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
validates :status, presence: true
# Helper methods for payment processors
%w[braintree stripe paddle_billing paddle_classic lemon_squeezy fake_processor].each do |processor_name|
define_method :"#{processor_name}?" do
customer.processor == processor_name
end
scope processor_name, -> { joins(:customer).where(pay_customers: {processor: processor_name}) }
end
delegate :owner, to: :customer
def self.find_by_processor_and_id(processor, processor_id)
joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
end
def sync!(**options)
self.class.sync(processor_id, **options)
reload
end
def skip_trial
self.trial_ends_at = nil
end
def generic_trial?
fake_processor? && trial_ends_at?
end
def has_trial?
trial_ends_at?
end
# Does not include the last second of the trial
def on_trial?
return false if ended?
trial_ends_at? && trial_ends_at > Time.current
end
def trial_ended?
return true if ended?
trial_ends_at? && trial_ends_at <= Time.current
end
def canceled?
ends_at?
end
def cancelled?
canceled?
end
def ended?
ends_at? && ends_at <= Time.current
end
def on_grace_period?
ends_at? && ends_at > Time.current
end
# If you cancel during a trial, you should still retain access until the end of the trial
# Otherwise a subscription is active unless it has ended or is currently paused
# Check the subscription status so we don't accidentally consider "incomplete", "unpaid", or other statuses as active
def active?
["trialing", "active"].include?(status) &&
(!(canceled? || paused?) || on_trial? || on_grace_period?)
end
def past_due?
status == "past_due"
end
def unpaid?
status == "unpaid"
end
def incomplete?
status == "incomplete"
end
def has_incomplete_payment?
past_due? || incomplete?
end
def swap_and_invoice(plan)
swap(plan)
customer.invoice!(subscription: processor_id)
end
private
def cancel_if_active
cancel_now! if active?
rescue => e
Rails.logger.info "[Pay] Unable to automatically cancel subscription `#{customer.processor} #{id}`: #{e.message}"
end
end
end
ActiveSupport.run_load_hooks :pay_subscription, Pay::Subscription