reji 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +73 -0
  4. data/.rubocop_todo.yml +31 -0
  5. data/Appraisals +2 -0
  6. data/Gemfile +1 -1
  7. data/README.md +41 -17
  8. data/Rakefile +8 -2
  9. data/app/controllers/reji/payment_controller.rb +4 -4
  10. data/app/controllers/reji/webhook_controller.rb +51 -62
  11. data/app/views/payment.html.erb +4 -4
  12. data/app/views/receipt.html.erb +16 -16
  13. data/config/routes.rb +2 -0
  14. data/gemfiles/rails_5.0.gemfile +9 -9
  15. data/gemfiles/rails_5.1.gemfile +7 -9
  16. data/gemfiles/rails_5.2.gemfile +7 -9
  17. data/gemfiles/rails_6.0.gemfile +7 -9
  18. data/lib/generators/reji/install/install_generator.rb +20 -24
  19. data/lib/generators/reji/install/templates/reji.rb +2 -2
  20. data/lib/reji.rb +12 -8
  21. data/lib/reji/concerns/manages_customer.rb +25 -29
  22. data/lib/reji/concerns/manages_invoices.rb +37 -44
  23. data/lib/reji/concerns/manages_payment_methods.rb +45 -62
  24. data/lib/reji/concerns/manages_subscriptions.rb +13 -13
  25. data/lib/reji/concerns/performs_charges.rb +7 -7
  26. data/lib/reji/concerns/prorates.rb +1 -1
  27. data/lib/reji/configuration.rb +2 -2
  28. data/lib/reji/engine.rb +2 -0
  29. data/lib/reji/errors.rb +9 -9
  30. data/lib/reji/invoice.rb +57 -56
  31. data/lib/reji/invoice_line_item.rb +21 -23
  32. data/lib/reji/payment.rb +9 -5
  33. data/lib/reji/payment_method.rb +8 -4
  34. data/lib/reji/subscription.rb +165 -183
  35. data/lib/reji/subscription_builder.rb +41 -49
  36. data/lib/reji/subscription_item.rb +26 -26
  37. data/lib/reji/tax.rb +8 -10
  38. data/lib/reji/version.rb +1 -1
  39. data/reji.gemspec +5 -4
  40. data/spec/dummy/app/models/user.rb +3 -7
  41. data/spec/dummy/application.rb +3 -7
  42. data/spec/dummy/db/schema.rb +3 -4
  43. data/spec/feature/charges_spec.rb +1 -1
  44. data/spec/feature/customer_spec.rb +1 -1
  45. data/spec/feature/invoices_spec.rb +6 -6
  46. data/spec/feature/multiplan_subscriptions_spec.rb +51 -53
  47. data/spec/feature/payment_methods_spec.rb +25 -25
  48. data/spec/feature/pending_updates_spec.rb +26 -26
  49. data/spec/feature/subscriptions_spec.rb +78 -78
  50. data/spec/feature/webhooks_spec.rb +72 -72
  51. data/spec/spec_helper.rb +2 -2
  52. data/spec/support/feature_helpers.rb +6 -12
  53. data/spec/unit/customer_spec.rb +13 -13
  54. data/spec/unit/invoice_line_item_spec.rb +12 -14
  55. data/spec/unit/invoice_spec.rb +7 -9
  56. data/spec/unit/payment_spec.rb +3 -3
  57. data/spec/unit/subscription_spec.rb +29 -30
  58. metadata +26 -11
  59. data/Gemfile.lock +0 -133
@@ -8,7 +8,7 @@ module Reji
8
8
 
9
9
  # Get the total amount that will be paid.
10
10
  def amount
11
- Reji.format_amount(self.raw_amount, @payment_intent.currency)
11
+ Reji.format_amount(raw_amount, @payment_intent.currency)
12
12
  end
13
13
 
14
14
  # Get the raw total amount that will be paid.
@@ -32,20 +32,20 @@ module Reji
32
32
  end
33
33
 
34
34
  # Determine if the payment was cancelled.
35
- def is_cancelled
35
+ def cancelled?
36
36
  @payment_intent.status == 'canceled'
37
37
  end
38
38
 
39
39
  # Determine if the payment was successful.
40
- def is_succeeded
40
+ def succeeded?
41
41
  @payment_intent.status == 'succeeded'
42
42
  end
43
43
 
44
44
  # Validate if the payment intent was successful and throw an exception if not.
45
45
  def validate
46
- raise Reji::PaymentFailureError::invalid_payment_method(self) if self.requires_payment_method
46
+ raise Reji::PaymentFailureError.invalid_payment_method(self) if requires_payment_method
47
47
 
48
- raise Reji::PaymentActionRequiredError::incomplete(self) if self.requires_action
48
+ raise Reji::PaymentActionRequiredError.incomplete(self) if requires_action
49
49
  end
50
50
 
51
51
  # The Stripe PaymentIntent instance.
@@ -57,5 +57,9 @@ module Reji
57
57
  def method_missing(key)
58
58
  @payment_intent[key]
59
59
  end
60
+
61
+ def respond_to_missing?(method_name, include_private = false)
62
+ super
63
+ end
60
64
  end
61
65
  end
@@ -3,7 +3,9 @@
3
3
  module Reji
4
4
  class PaymentMethod
5
5
  def initialize(owner, payment_method)
6
- raise Reji::InvalidPaymentMethodError.invalid_owner(payment_method, owner) if owner.stripe_id != payment_method.customer
6
+ if owner.stripe_id != payment_method.customer
7
+ raise Reji::InvalidPaymentMethodError.invalid_owner(payment_method, owner)
8
+ end
7
9
 
8
10
  @owner = owner
9
11
  @payment_method = payment_method
@@ -15,9 +17,7 @@ module Reji
15
17
  end
16
18
 
17
19
  # Get the Stripe model instance.
18
- def owner
19
- @owner
20
- end
20
+ attr_reader :owner
21
21
 
22
22
  # Get the Stripe PaymentMethod instance.
23
23
  def as_stripe_payment_method
@@ -28,5 +28,9 @@ module Reji
28
28
  def method_missing(key)
29
29
  @payment_method[key]
30
30
  end
31
+
32
+ def respond_to_missing?(method_name, include_private = false)
33
+ super
34
+ end
31
35
  end
32
36
  end
@@ -10,8 +10,8 @@ module Reji
10
10
 
11
11
  scope :incomplete, -> { where(stripe_status: 'incomplete') }
12
12
  scope :past_due, -> { where(stripe_status: 'past_due') }
13
- scope :active, -> {
14
- query = (where(ends_at: nil).or(on_grace_period))
13
+ scope :active, lambda {
14
+ query = where(ends_at: nil).or(on_grace_period)
15
15
  .where('stripe_status != ?', 'incomplete')
16
16
  .where('stripe_status != ?', 'incomplete_expired')
17
17
  .where('stripe_status != ?', 'unpaid')
@@ -24,171 +24,171 @@ module Reji
24
24
  scope :cancelled, -> { where.not(ends_at: nil) }
25
25
  scope :not_cancelled, -> { where(ends_at: nil) }
26
26
  scope :ended, -> { cancelled.not_on_grace_period }
27
- scope :on_trial, -> { where.not(trial_ends_at: nil).where('trial_ends_at > ?', Time.now) }
28
- scope :not_on_trial, -> { where(trial_ends_at: nil).or(where('trial_ends_at <= ?', Time.now)) }
29
- scope :on_grace_period, -> { where.not(ends_at: nil).where('ends_at > ?', Time.now) }
30
- scope :not_on_grace_period, -> { where(ends_at: nil).or(where('ends_at <= ?', Time.now)) }
27
+ scope :on_trial, -> { where.not(trial_ends_at: nil).where('trial_ends_at > ?', Time.current) }
28
+ scope :not_on_trial, -> { where(trial_ends_at: nil).or(where('trial_ends_at <= ?', Time.current)) }
29
+ scope :on_grace_period, -> { where.not(ends_at: nil).where('ends_at > ?', Time.current) }
30
+ scope :not_on_grace_period, -> { where(ends_at: nil).or(where('ends_at <= ?', Time.current)) }
31
31
 
32
32
  # The date on which the billing cycle should be anchored.
33
33
  @billing_cycle_anchor = nil
34
34
 
35
35
  # Get the user that owns the subscription.
36
36
  def user
37
- self.owner
37
+ owner
38
38
  end
39
39
 
40
40
  # Determine if the subscription has multiple plans.
41
- def has_multiple_plans
42
- self.stripe_plan.nil?
41
+ def multiple_plans?
42
+ stripe_plan.nil?
43
43
  end
44
44
 
45
45
  # Determine if the subscription has a single plan.
46
- def has_single_plan
47
- ! self.has_multiple_plans
46
+ def single_plan?
47
+ !multiple_plans?
48
48
  end
49
49
 
50
50
  # Determine if the subscription has a specific plan.
51
- def has_plan(plan)
52
- return self.items.any? { |item| item.stripe_plan == plan } if self.has_multiple_plans
51
+ def plan?(plan)
52
+ return items.any? { |item| item.stripe_plan == plan } if multiple_plans?
53
53
 
54
- self.stripe_plan == plan
54
+ stripe_plan == plan
55
55
  end
56
56
 
57
57
  # Get the subscription item for the given plan.
58
58
  def find_item_or_fail(plan)
59
- self.items.where(stripe_plan: plan).first
59
+ items.where(stripe_plan: plan).first
60
60
  end
61
61
 
62
62
  # Determine if the subscription is active, on trial, or within its grace period.
63
63
  def valid
64
- self.active || self.on_trial || self.on_grace_period
64
+ active || on_trial || on_grace_period
65
65
  end
66
66
 
67
67
  # Determine if the subscription is incomplete.
68
68
  def incomplete
69
- self.stripe_status == 'incomplete'
69
+ stripe_status == 'incomplete'
70
70
  end
71
71
 
72
72
  # Determine if the subscription is past due.
73
73
  def past_due
74
- self.stripe_status == 'past_due'
74
+ stripe_status == 'past_due'
75
75
  end
76
76
 
77
77
  # Determine if the subscription is active.
78
78
  def active
79
- (self.ends_at.nil? || self.on_grace_period) &&
80
- self.stripe_status != 'incomplete' &&
81
- self.stripe_status != 'incomplete_expired' &&
82
- self.stripe_status != 'unpaid' &&
83
- (! Reji.deactivate_past_due || self.stripe_status != 'past_due')
79
+ (ends_at.nil? || on_grace_period) &&
80
+ stripe_status != 'incomplete' &&
81
+ stripe_status != 'incomplete_expired' &&
82
+ stripe_status != 'unpaid' &&
83
+ (!Reji.deactivate_past_due || stripe_status != 'past_due')
84
84
  end
85
85
 
86
86
  # Sync the Stripe status of the subscription.
87
87
  def sync_stripe_status
88
- subscription = self.as_stripe_subscription
88
+ subscription = as_stripe_subscription
89
89
 
90
- self.update({stripe_status: subscription.status})
90
+ update({ stripe_status: subscription.status })
91
91
  end
92
92
 
93
93
  # Determine if the subscription is recurring and not on trial.
94
94
  def recurring
95
- ! self.on_trial && ! self.cancelled
95
+ !on_trial && !cancelled
96
96
  end
97
97
 
98
98
  # Determine if the subscription is no longer active.
99
99
  def cancelled
100
- ! self.ends_at.nil?
100
+ !ends_at.nil?
101
101
  end
102
102
 
103
103
  # Determine if the subscription has ended and the grace period has expired.
104
104
  def ended
105
- !! (self.cancelled && ! self.on_grace_period)
105
+ !!(cancelled && !on_grace_period)
106
106
  end
107
107
 
108
108
  # Determine if the subscription is within its trial period.
109
109
  def on_trial
110
- !! (self.trial_ends_at && self.trial_ends_at.future?)
110
+ !!(trial_ends_at && trial_ends_at.future?)
111
111
  end
112
112
 
113
113
  # Determine if the subscription is within its grace period after cancellation.
114
114
  def on_grace_period
115
- !! (self.ends_at && self.ends_at.future?)
115
+ !!(ends_at && ends_at.future?)
116
116
  end
117
117
 
118
118
  # Increment the quantity of the subscription.
119
119
  def increment_quantity(count = 1, plan = nil)
120
- self.guard_against_incomplete
120
+ guard_against_incomplete
121
121
 
122
122
  if plan
123
- self.find_item_or_fail(plan)
124
- .set_proration_behavior(self.prorate_behavior)
123
+ find_item_or_fail(plan)
124
+ .set_proration_behavior(proration_behavior)
125
125
  .increment_quantity(count)
126
126
 
127
127
  return self
128
128
  end
129
129
 
130
- self.guard_against_multiple_plans
130
+ guard_against_multiple_plans
131
131
 
132
- self.update_quantity(self.quantity + count, plan)
132
+ update_quantity(quantity + count, plan)
133
133
  end
134
134
 
135
135
  # Increment the quantity of the subscription, and invoice immediately.
136
136
  def increment_and_invoice(count = 1, plan = nil)
137
- self.guard_against_incomplete
137
+ guard_against_incomplete
138
138
 
139
- self.always_invoice
139
+ always_invoice
140
140
 
141
141
  if plan
142
- self.find_item_or_fail(plan)
143
- .set_proration_behavior(self.prorate_behavior)
142
+ find_item_or_fail(plan)
143
+ .set_proration_behavior(proration_behavior)
144
144
  .increment_quantity(count)
145
145
 
146
146
  return self
147
147
  end
148
148
 
149
- self.guard_against_multiple_plans
149
+ guard_against_multiple_plans
150
150
 
151
- self.increment_quantity(count, plan)
151
+ increment_quantity(count, plan)
152
152
  end
153
153
 
154
154
  # Decrement the quantity of the subscription.
155
155
  def decrement_quantity(count = 1, plan = nil)
156
- self.guard_against_incomplete
156
+ guard_against_incomplete
157
157
 
158
158
  if plan
159
- self.find_item_or_fail(plan)
160
- .set_proration_behavior(self.prorate_behavior)
159
+ find_item_or_fail(plan)
160
+ .set_proration_behavior(proration_behavior)
161
161
  .decrement_quantity(count)
162
162
 
163
163
  return self
164
164
  end
165
165
 
166
- self.guard_against_multiple_plans
166
+ guard_against_multiple_plans
167
167
 
168
- self.update_quantity([1, self.quantity - count].max, plan)
168
+ update_quantity([1, quantity - count].max, plan)
169
169
  end
170
170
 
171
171
  # Update the quantity of the subscription.
172
172
  def update_quantity(quantity, plan = nil)
173
- self.guard_against_incomplete
173
+ guard_against_incomplete
174
174
 
175
175
  if plan
176
- self.find_item_or_fail(plan)
177
- .set_proration_behavior(self.prorate_behavior)
176
+ find_item_or_fail(plan)
177
+ .set_proration_behavior(proration_behavior)
178
178
  .update_quantity(quantity)
179
179
 
180
180
  return self
181
181
  end
182
182
 
183
- self.guard_against_multiple_plans
183
+ guard_against_multiple_plans
184
184
 
185
- stripe_subscription = self.as_stripe_subscription
185
+ stripe_subscription = as_stripe_subscription
186
186
  stripe_subscription.quantity = quantity
187
- stripe_subscription.payment_behavior = self.payment_behavior
188
- stripe_subscription.proration_behavior = self.prorate_behavior
187
+ stripe_subscription.payment_behavior = payment_behavior
188
+ stripe_subscription.proration_behavior = proration_behavior
189
189
  stripe_subscription.save
190
190
 
191
- self.update(quantity: quantity)
191
+ update(quantity: quantity)
192
192
 
193
193
  self
194
194
  end
@@ -209,13 +209,13 @@ module Reji
209
209
 
210
210
  # Extend an existing subscription's trial period.
211
211
  def extend_trial(date)
212
- raise ArgumentError.new("Extending a subscription's trial requires a date in the future.") unless date.future?
212
+ raise ArgumentError, "Extending a subscription's trial requires a date in the future." unless date.future?
213
213
 
214
- subscription = self.as_stripe_subscription
214
+ subscription = as_stripe_subscription
215
215
  subscription.trial_end = date.to_i
216
216
  subscription.save
217
217
 
218
- self.update(trial_ends_at: date)
218
+ update(trial_ends_at: date)
219
219
 
220
220
  self
221
221
  end
@@ -224,25 +224,23 @@ module Reji
224
224
  def swap(plans, options = {})
225
225
  plans = [plans] unless plans.instance_of? Array
226
226
 
227
- raise ArgumentError.new('Please provide at least one plan when swapping.') if plans.empty?
227
+ raise ArgumentError, 'Please provide at least one plan when swapping.' if plans.empty?
228
228
 
229
- self.guard_against_incomplete
229
+ guard_against_incomplete
230
230
 
231
- items = self.merge_items_that_should_be_deleted_during_swap(
232
- self.parse_swap_plans(plans)
233
- )
231
+ items = merge_items_that_should_be_deleted_during_swap(parse_swap_plans(plans))
234
232
 
235
- stripe_subscription = Stripe::Subscription::update(
236
- self.stripe_id,
237
- self.get_swap_options(items, options),
238
- self.owner.stripe_options
233
+ stripe_subscription = Stripe::Subscription.update(
234
+ stripe_id,
235
+ get_swap_options(items, options),
236
+ owner.stripe_options
239
237
  )
240
238
 
241
- self.update({
242
- :stripe_status => stripe_subscription.status,
243
- :stripe_plan => stripe_subscription.plan ? stripe_subscription.plan.id : nil,
244
- :quantity => stripe_subscription.quantity,
245
- :ends_at => nil,
239
+ update({
240
+ stripe_status: stripe_subscription.status,
241
+ stripe_plan: stripe_subscription.plan ? stripe_subscription.plan.id : nil,
242
+ quantity: stripe_subscription.quantity,
243
+ ends_at: nil,
246
244
  })
247
245
 
248
246
  stripe_subscription.items.each do |item|
@@ -255,48 +253,46 @@ module Reji
255
253
  # Delete items that aren't attached to the subscription anymore...
256
254
  self.items.where('stripe_plan NOT IN (?)', items.values.pluck(:plan).compact).destroy_all
257
255
 
258
- if self.has_incomplete_payment
259
- Payment.new(stripe_subscription.latest_invoice.payment_intent).validate
260
- end
256
+ Payment.new(stripe_subscription.latest_invoice.payment_intent).validate if incomplete_payment?
261
257
 
262
258
  self
263
259
  end
264
260
 
265
261
  # Swap the subscription to new Stripe plans, and invoice immediately.
266
262
  def swap_and_invoice(plans, options = {})
267
- self.always_invoice
263
+ always_invoice
268
264
 
269
- self.swap(plans, options)
265
+ swap(plans, options)
270
266
  end
271
267
 
272
268
  # Add a new Stripe plan to the subscription.
273
269
  def add_plan(plan, quantity = 1, options = {})
274
- self.guard_against_incomplete
270
+ guard_against_incomplete
275
271
 
276
- if self.items.any? { |item| item.stripe_plan == plan }
277
- raise Reji::SubscriptionUpdateFailureError::duplicate_plan(self, plan)
272
+ if items.any? { |item| item.stripe_plan == plan }
273
+ raise Reji::SubscriptionUpdateFailureError.duplicate_plan(self, plan)
278
274
  end
279
275
 
280
- subscription = self.as_stripe_subscription
276
+ subscription = as_stripe_subscription
281
277
 
282
278
  item = subscription.items.create({
283
- :plan => plan,
284
- :quantity => quantity,
285
- :tax_rates => self.get_plan_tax_rates_for_payload(plan),
286
- :payment_behavior => self.payment_behavior,
287
- :proration_behavior => self.prorate_behavior,
279
+ plan: plan,
280
+ quantity: quantity,
281
+ tax_rates: get_plan_tax_rates_for_payload(plan),
282
+ payment_behavior: payment_behavior,
283
+ proration_behavior: proration_behavior,
288
284
  }.merge(options))
289
285
 
290
- self.items.create({
291
- :stripe_id => item.id,
292
- :stripe_plan => plan,
293
- :quantity => quantity
286
+ items.create({
287
+ stripe_id: item.id,
288
+ stripe_plan: plan,
289
+ quantity: quantity,
294
290
  })
295
291
 
296
- if self.has_single_plan
297
- self.update({
298
- :stripe_plan => nil,
299
- :quantity => nil,
292
+ if single_plan?
293
+ update({
294
+ stripe_plan: nil,
295
+ quantity: nil,
300
296
  })
301
297
  end
302
298
 
@@ -305,29 +301,29 @@ module Reji
305
301
 
306
302
  # Add a new Stripe plan to the subscription, and invoice immediately.
307
303
  def add_plan_and_invoice(plan, quantity = 1, options = {})
308
- self.always_invoice
304
+ always_invoice
309
305
 
310
- self.add_plan(plan, quantity, options)
306
+ add_plan(plan, quantity, options)
311
307
  end
312
308
 
313
309
  # Remove a Stripe plan from the subscription.
314
310
  def remove_plan(plan)
315
- raise Reji::SubscriptionUpdateFailureError::cannot_delete_last_plan(self) if self.has_single_plan
311
+ raise Reji::SubscriptionUpdateFailureError.cannot_delete_last_plan(self) if single_plan?
316
312
 
317
- item = self.find_item_or_fail(plan)
313
+ item = find_item_or_fail(plan)
318
314
 
319
315
  item.as_stripe_subscription_item.delete({
320
- :proration_behavior => self.prorate_behavior
316
+ proration_behavior: proration_behavior,
321
317
  })
322
318
 
323
- self.items.where(stripe_plan: plan).destroy_all
319
+ items.where(stripe_plan: plan).destroy_all
324
320
 
325
- if self.items.count < 2
326
- item = self.items.first
321
+ if items.count < 2
322
+ item = items.first
327
323
 
328
- self.update({
329
- :stripe_plan => item.stripe_plan,
330
- :quantity => quantity,
324
+ update({
325
+ stripe_plan: item.stripe_plan,
326
+ quantity: quantity,
331
327
  })
332
328
  end
333
329
 
@@ -336,7 +332,7 @@ module Reji
336
332
 
337
333
  # Cancel the subscription at the end of the billing period.
338
334
  def cancel
339
- subscription = self.as_stripe_subscription
335
+ subscription = as_stripe_subscription
340
336
 
341
337
  subscription.cancel_at_period_end = true
342
338
 
@@ -347,70 +343,62 @@ module Reji
347
343
  # If the user was on trial, we will set the grace period to end when the trial
348
344
  # would have ended. Otherwise, we'll retrieve the end of the billing period
349
345
  # period and make that the end of the grace period for this current user.
350
- if self.on_trial
351
- self.ends_at = self.trial_ends_at
352
- else
353
- self.ends_at = Time.at(subscription.current_period_end)
354
- end
346
+ self.ends_at = on_trial ? trial_ends_at : Time.zone.at(subscription.current_period_end)
355
347
 
356
- self.save
348
+ save
357
349
 
358
350
  self
359
351
  end
360
352
 
361
353
  # Cancel the subscription immediately.
362
354
  def cancel_now
363
- self.as_stripe_subscription.cancel({
364
- :prorate => self.prorate_behavior == 'create_prorations',
355
+ as_stripe_subscription.cancel({
356
+ prorate: proration_behavior == 'create_prorations',
365
357
  })
366
358
 
367
- self.mark_as_cancelled
359
+ mark_as_cancelled
368
360
 
369
361
  self
370
362
  end
371
363
 
372
364
  # Cancel the subscription and invoice immediately.
373
365
  def cancel_now_and_invoice
374
- self.as_stripe_subscription.cancel({
375
- :invoice_now => true,
376
- :prorate => self.prorate_behavior == 'create_prorations',
366
+ as_stripe_subscription.cancel({
367
+ invoice_now: true,
368
+ prorate: proration_behavior == 'create_prorations',
377
369
  })
378
370
 
379
- self.mark_as_cancelled
371
+ mark_as_cancelled
380
372
 
381
373
  self
382
374
  end
383
375
 
384
376
  # Mark the subscription as cancelled.
385
377
  def mark_as_cancelled
386
- self.update({
387
- :stripe_status => 'canceled',
388
- :ends_at => Time.now,
378
+ update({
379
+ stripe_status: 'canceled',
380
+ ends_at: Time.current,
389
381
  })
390
382
  end
391
383
 
392
384
  # Resume the cancelled subscription.
393
385
  def resume
394
- raise ArgumentError.new('Unable to resume subscription that is not within grace period.') unless self.on_grace_period
386
+ raise ArgumentError, 'Unable to resume subscription that is not within grace period.' unless on_grace_period
395
387
 
396
- subscription = self.as_stripe_subscription
388
+ subscription = as_stripe_subscription
397
389
 
398
390
  subscription.cancel_at_period_end = false
399
391
 
400
- if self.on_trial
401
- subscription.trial_end = Time.at(self.trial_ends_at).to_i
402
- else
403
- subscription.trial_end = 'now'
404
- end
392
+ subscription.trial_end = on_trial ? Time.zone.at(trial_ends_at).to_i : 'now'
405
393
 
406
394
  subscription = subscription.save
407
395
 
408
396
  # Finally, we will remove the ending timestamp from the user's record in the
409
397
  # local database to indicate that the subscription is active again and is
410
398
  # no longer "cancelled". Then we will save this record in the database.
411
- self.update({
412
- :stripe_status => subscription.status,
413
- :ends_at => nil,
399
+ update({
400
+ stripe_status: subscription.status,
401
+ ends_at: nil,
414
402
  })
415
403
 
416
404
  self
@@ -418,51 +406,49 @@ module Reji
418
406
 
419
407
  # Determine if the subscription has pending updates.
420
408
  def pending
421
- ! self.as_stripe_subscription.pending_update.nil?
409
+ !as_stripe_subscription.pending_update.nil?
422
410
  end
423
411
 
424
412
  # Invoice the subscription outside of the regular billing cycle.
425
413
  def invoice(options = {})
426
- begin
427
- self.user.invoice(options.merge({
428
- :subscription => self.stripe_id
429
- }))
430
- rescue IncompletePaymentError => e
431
- # Set the new Stripe subscription status immediately when payment fails...
432
- self.update(stripe_status: e.payment.invoice.subscription.status)
433
-
434
- raise e
435
- end
414
+ user.invoice(options.merge({
415
+ subscription: stripe_id,
416
+ }))
417
+ rescue IncompletePaymentError => e
418
+ # Set the new Stripe subscription status immediately when payment fails...
419
+ update(stripe_status: e.payment.invoice.subscription.status)
420
+
421
+ raise e
436
422
  end
437
423
 
438
424
  # Get the latest invoice for the subscription.
439
425
  def latest_invoice
440
- stripe_subscription = self.as_stripe_subscription(['latest_invoice'])
426
+ stripe_subscription = as_stripe_subscription(['latest_invoice'])
441
427
 
442
- Invoice.new(self.user, stripe_subscription.latest_invoice)
428
+ Invoice.new(user, stripe_subscription.latest_invoice)
443
429
  end
444
430
 
445
431
  # Sync the tax percentage of the user to the subscription.
446
432
  def sync_tax_percentage
447
- subscription = self.as_stripe_subscription
433
+ subscription = as_stripe_subscription
448
434
 
449
- subscription.tax_percentage = self.user.tax_percentage
435
+ subscription.tax_percentage = user.tax_percentage
450
436
 
451
437
  subscription.save
452
438
  end
453
439
 
454
440
  # Sync the tax rates of the user to the subscription.
455
441
  def sync_tax_rates
456
- subscription = self.as_stripe_subscription
442
+ subscription = as_stripe_subscription
457
443
 
458
- subscription.default_tax_rates = self.user.tax_rates
444
+ subscription.default_tax_rates = user.tax_rates
459
445
 
460
446
  subscription.save
461
447
 
462
- self.items.each do |item|
448
+ items.each do |item|
463
449
  stripe_subscription_item = item.as_stripe_subscription_item
464
450
 
465
- stripe_subscription_item.tax_rates = self.get_plan_tax_rates_for_payload(item.stripe_plan)
451
+ stripe_subscription_item.tax_rates = get_plan_tax_rates_for_payload(item.stripe_plan)
466
452
 
467
453
  stripe_subscription_item.save
468
454
  end
@@ -470,21 +456,19 @@ module Reji
470
456
 
471
457
  # Get the plan tax rates for the Stripe payload.
472
458
  def get_plan_tax_rates_for_payload(plan)
473
- tax_rates = self.user.plan_tax_rates
459
+ tax_rates = user.plan_tax_rates
474
460
 
475
- if tax_rates
476
- tax_rates.key?(plan) ? tax_rates[plan] : nil
477
- end
461
+ tax_rates[plan] || nil unless tax_rates.empty?
478
462
  end
479
463
 
480
464
  # Determine if the subscription has an incomplete payment.
481
- def has_incomplete_payment
482
- self.past_due || self.incomplete
465
+ def incomplete_payment?
466
+ past_due || incomplete
483
467
  end
484
468
 
485
469
  # Get the latest payment for a Subscription.
486
470
  def latest_payment
487
- payment_intent = self.as_stripe_subscription(['latest_invoice.payment_intent'])
471
+ payment_intent = as_stripe_subscription(['latest_invoice.payment_intent'])
488
472
  .latest_invoice
489
473
  .payment_intent
490
474
 
@@ -493,64 +477,62 @@ module Reji
493
477
 
494
478
  # Make sure a subscription is not incomplete when performing changes.
495
479
  def guard_against_incomplete
496
- raise Reji::SubscriptionUpdateFailureError.incomplete_subscription(self) if self.incomplete
480
+ raise Reji::SubscriptionUpdateFailureError.incomplete_subscription(self) if incomplete
497
481
  end
498
482
 
499
483
  # Make sure a plan argument is provided when the subscription is a multi plan subscription.
500
484
  def guard_against_multiple_plans
501
- raise ArgumentError.new('This method requires a plan argument since the subscription has multiple plans.') if self.has_multiple_plans
485
+ return unless multiple_plans?
486
+
487
+ raise ArgumentError, 'This method requires a plan argument since the subscription has multiple plans.'
502
488
  end
503
489
 
504
490
  # Update the underlying Stripe subscription information for the model.
505
491
  def update_stripe_subscription(options = {})
506
492
  Stripe::Subscription.update(
507
- self.stripe_id, options, self.owner.stripe_options
493
+ stripe_id, options, owner.stripe_options
508
494
  )
509
495
  end
510
496
 
511
497
  # Get the subscription as a Stripe subscription object.
512
498
  def as_stripe_subscription(expand = {})
513
- Stripe::Subscription::retrieve(
514
- {:id => self.stripe_id, :expand => expand}, self.owner.stripe_options
499
+ Stripe::Subscription.retrieve(
500
+ { id: stripe_id, expand: expand }, owner.stripe_options
515
501
  )
516
502
  end
517
503
 
518
- protected
519
-
520
504
  # Parse the given plans for a swap operation.
521
- def parse_swap_plans(plans)
522
- plans.map {
523
- |plan| [plan, {
524
- :plan => plan,
525
- :tax_rates => self.get_plan_tax_rates_for_payload(plan)
526
- }]
527
- }.to_h
505
+ protected def parse_swap_plans(plans)
506
+ plans.map do |plan|
507
+ [plan, {
508
+ plan: plan,
509
+ tax_rates: get_plan_tax_rates_for_payload(plan),
510
+ },]
511
+ end.to_h
528
512
  end
529
513
 
530
514
  # Merge the items that should be deleted during swap into the given items collection.
531
- def merge_items_that_should_be_deleted_during_swap(items)
532
- self.as_stripe_subscription.items.data.each do |stripe_subscription_item|
515
+ protected def merge_items_that_should_be_deleted_during_swap(items)
516
+ as_stripe_subscription.items.data.each do |stripe_subscription_item|
533
517
  plan = stripe_subscription_item.plan.id
534
518
 
535
519
  item = items.key?(plan) ? items[plan] : {}
536
520
 
537
- if item.empty?
538
- item[:deleted] = true
539
- end
521
+ item[:deleted] = true if item.empty?
540
522
 
541
- items[plan] = item.merge({:id => stripe_subscription_item.id})
523
+ items[plan] = item.merge({ id: stripe_subscription_item.id })
542
524
  end
543
525
 
544
526
  items
545
527
  end
546
528
 
547
529
  # Get the options array for a swap operation.
548
- def get_swap_options(items, options)
530
+ protected def get_swap_options(items, options)
549
531
  payload = {
550
- :items => items.values,
551
- :payment_behavior => self.payment_behavior,
552
- :proration_behavior => self.prorate_behavior,
553
- :expand => ['latest_invoice.payment_intent'],
532
+ items: items.values,
533
+ payment_behavior: payment_behavior,
534
+ proration_behavior: proration_behavior,
535
+ expand: ['latest_invoice.payment_intent'],
554
536
  }
555
537
 
556
538
  payload[:cancel_at_period_end] = false if payload[:payment_behavior] != 'pending_if_incomplete'
@@ -559,7 +541,7 @@ module Reji
559
541
 
560
542
  payload[:billing_cycle_anchor] = @billing_cycle_anchor unless @billing_cycle_anchor.nil?
561
543
 
562
- payload[:trial_end] = self.on_trial ? self.trial_ends_at : 'now'
544
+ payload[:trial_end] = on_trial ? trial_ends_at : 'now'
563
545
 
564
546
  payload
565
547
  end