reji 1.0.0 → 1.1.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.
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
@@ -9,11 +9,11 @@ module Reji
9
9
  def initialize(owner, name, plans = [])
10
10
  @owner = owner
11
11
  @name = name
12
- @trial_expires # The date and time the trial will expire.
12
+ @trial_expires = nil # The date and time the trial will expire.
13
13
  @skip_trial = false # Indicates that the trial should end immediately.
14
14
  @billing_cycle_anchor = nil # The date on which the billing cycle should be anchored.
15
- @coupon # The coupon code being applied to the customer.
16
- @metadata # The metadata to apply to the subscription.
15
+ @coupon = nil # The coupon code being applied to the customer.
16
+ @metadata = nil # The metadata to apply to the subscription.
17
17
  @items = {}
18
18
 
19
19
  plans = [plans] unless plans.instance_of? Array
@@ -24,11 +24,11 @@ module Reji
24
24
  # Set a plan on the subscription builder.
25
25
  def plan(plan, quantity = 1)
26
26
  options = {
27
- :plan => plan,
28
- :quantity => quantity
27
+ plan: plan,
28
+ quantity: quantity,
29
29
  }
30
30
 
31
- tax_rates = self.get_plan_tax_rates_for_payload(plan)
31
+ tax_rates = get_plan_tax_rates_for_payload(plan)
32
32
 
33
33
  options[:tax_rates] = tax_rates if tax_rates
34
34
 
@@ -40,7 +40,7 @@ module Reji
40
40
  # Specify the quantity of a subscription item.
41
41
  def quantity(quantity, plan = nil)
42
42
  if plan.nil?
43
- raise ArgumentError.new('Plan is required when creating multi-plan subscriptions.') if @items.length > 1
43
+ raise ArgumentError, 'Plan is required when creating multi-plan subscriptions.' if @items.length > 1
44
44
 
45
45
  plan = @items.values[0][:plan]
46
46
  end
@@ -50,7 +50,7 @@ module Reji
50
50
 
51
51
  # Specify the number of days of the trial.
52
52
  def trial_days(trial_days)
53
- @trial_expires = Time.now + trial_days.days
53
+ @trial_expires = Time.current + trial_days.days
54
54
 
55
55
  self
56
56
  end
@@ -92,16 +92,14 @@ module Reji
92
92
 
93
93
  # Add a new Stripe subscription to the Stripe model.
94
94
  def add(customer_options = {}, subscription_options = {})
95
- self.create(nil, customer_options, subscription_options)
95
+ create(nil, customer_options, subscription_options)
96
96
  end
97
97
 
98
98
  # Create a new Stripe subscription.
99
99
  def create(payment_method = nil, customer_options = {}, subscription_options = {})
100
- customer = self.get_stripe_customer(payment_method, customer_options)
100
+ customer = get_stripe_customer(payment_method, customer_options)
101
101
 
102
- payload = {:customer => customer.id}
103
- .merge(self.build_payload)
104
- .merge(subscription_options)
102
+ payload = { customer: customer.id }.merge(build_payload).merge(subscription_options)
105
103
 
106
104
  stripe_subscription = Stripe::Subscription.create(
107
105
  payload,
@@ -109,34 +107,30 @@ module Reji
109
107
  )
110
108
 
111
109
  subscription = @owner.subscriptions.create({
112
- :name => @name,
113
- :stripe_id => stripe_subscription.id,
114
- :stripe_status => stripe_subscription.status,
115
- :stripe_plan => stripe_subscription.plan ? stripe_subscription.plan.id : nil,
116
- :quantity => stripe_subscription.quantity,
117
- :trial_ends_at => @skip_trial ? nil : @trial_expires,
118
- :ends_at => nil,
110
+ name: @name,
111
+ stripe_id: stripe_subscription.id,
112
+ stripe_status: stripe_subscription.status,
113
+ stripe_plan: stripe_subscription.plan ? stripe_subscription.plan.id : nil,
114
+ quantity: stripe_subscription.quantity,
115
+ trial_ends_at: @skip_trial ? nil : @trial_expires,
116
+ ends_at: nil,
119
117
  })
120
118
 
121
119
  stripe_subscription.items.each do |item|
122
120
  subscription.items.create({
123
- :stripe_id => item.id,
124
- :stripe_plan => item.plan.id,
125
- :quantity => item.quantity,
121
+ stripe_id: item.id,
122
+ stripe_plan: item.plan.id,
123
+ quantity: item.quantity,
126
124
  })
127
125
  end
128
126
 
129
- if subscription.has_incomplete_payment
130
- Payment.new(stripe_subscription.latest_invoice.payment_intent).validate
131
- end
127
+ Payment.new(stripe_subscription.latest_invoice.payment_intent).validate if subscription.incomplete_payment?
132
128
 
133
129
  subscription
134
130
  end
135
131
 
136
- protected
137
-
138
132
  # Get the Stripe customer instance for the current user and payment method.
139
- def get_stripe_customer(payment_method = nil, options = {})
133
+ protected def get_stripe_customer(payment_method = nil, options = {})
140
134
  customer = @owner.create_or_get_stripe_customer(options)
141
135
 
142
136
  @owner.update_default_payment_method(payment_method) if payment_method
@@ -145,20 +139,20 @@ module Reji
145
139
  end
146
140
 
147
141
  # Build the payload for subscription creation.
148
- def build_payload
142
+ protected def build_payload
149
143
  payload = {
150
- :billing_cycle_anchor => @billing_cycle_anchor,
151
- :coupon => @coupon,
152
- :expand => ['latest_invoice.payment_intent'],
153
- :metadata => @metadata,
154
- :items => @items.values,
155
- :payment_behavior => self.payment_behavior,
156
- :proration_behavior => self.prorate_behavior,
157
- :trial_end => self.get_trial_end_for_payload,
158
- :off_session => true,
144
+ billing_cycle_anchor: @billing_cycle_anchor,
145
+ coupon: @coupon,
146
+ expand: ['latest_invoice.payment_intent'],
147
+ metadata: @metadata,
148
+ items: @items.values,
149
+ payment_behavior: payment_behavior,
150
+ proration_behavior: proration_behavior,
151
+ trial_end: get_trial_end_for_payload,
152
+ off_session: true,
159
153
  }
160
154
 
161
- tax_rates = self.get_tax_rates_for_payload
155
+ tax_rates = get_tax_rates_for_payload
162
156
 
163
157
  if tax_rates
164
158
  payload[:default_tax_rates] = tax_rates
@@ -166,7 +160,7 @@ module Reji
166
160
  return payload
167
161
  end
168
162
 
169
- tax_percentage = self.get_tax_percentage_for_payload
163
+ tax_percentage = get_tax_percentage_for_payload
170
164
 
171
165
  payload[:tax_percent] = tax_percentage if tax_percentage
172
166
 
@@ -174,33 +168,31 @@ module Reji
174
168
  end
175
169
 
176
170
  # Get the trial ending date for the Stripe payload.
177
- def get_trial_end_for_payload
171
+ protected def get_trial_end_for_payload
178
172
  return 'now' if @skip_trial
179
173
 
180
- @trial_expires.to_i if @trial_expires
174
+ @trial_expires&.to_i
181
175
  end
182
176
 
183
177
  # Get the tax percentage for the Stripe payload.
184
- def get_tax_percentage_for_payload
178
+ protected def get_tax_percentage_for_payload
185
179
  tax_percentage = @owner.tax_percentage
186
180
 
187
181
  tax_percentage if tax_percentage > 0
188
182
  end
189
183
 
190
184
  # Get the tax rates for the Stripe payload.
191
- def get_tax_rates_for_payload
185
+ protected def get_tax_rates_for_payload
192
186
  tax_rates = @owner.tax_rates
193
187
 
194
188
  tax_rates unless tax_rates.empty?
195
189
  end
196
190
 
197
191
  # Get the plan tax rates for the Stripe payload.
198
- def get_plan_tax_rates_for_payload(plan)
192
+ protected def get_plan_tax_rates_for_payload(plan)
199
193
  tax_rates = @owner.plan_tax_rates
200
194
 
201
- unless tax_rates.empty?
202
- tax_rates[plan] || nil
203
- end
195
+ tax_rates[plan] || nil unless tax_rates.empty?
204
196
  end
205
197
  end
206
198
  end
@@ -9,88 +9,88 @@ module Reji
9
9
 
10
10
  # Increment the quantity of the subscription item.
11
11
  def increment_quantity(count = 1)
12
- self.update_quantity(self.quantity + count)
12
+ update_quantity(quantity + count)
13
13
 
14
14
  self
15
15
  end
16
16
 
17
17
  # Increment the quantity of the subscription item, and invoice immediately.
18
18
  def increment_and_invoice(count = 1)
19
- self.always_invoice
19
+ always_invoice
20
20
 
21
- self.increment_quantity(count)
21
+ increment_quantity(count)
22
22
 
23
23
  self
24
24
  end
25
25
 
26
26
  # Decrement the quantity of the subscription item.
27
27
  def decrement_quantity(count = 1)
28
- self.update_quantity([1, self.quantity - count].max)
28
+ update_quantity([1, quantity - count].max)
29
29
 
30
30
  self
31
31
  end
32
32
 
33
33
  # Update the quantity of the subscription item.
34
34
  def update_quantity(quantity)
35
- self.subscription.guard_against_incomplete
35
+ subscription.guard_against_incomplete
36
36
 
37
- stripe_subscription_item = self.as_stripe_subscription_item
37
+ stripe_subscription_item = as_stripe_subscription_item
38
38
  stripe_subscription_item.quantity = quantity
39
- stripe_subscription_item.payment_behavior = self.payment_behavior
40
- stripe_subscription_item.proration_behavior = self.prorate_behavior
39
+ stripe_subscription_item.payment_behavior = payment_behavior
40
+ stripe_subscription_item.proration_behavior = proration_behavior
41
41
  stripe_subscription_item.save
42
42
 
43
- self.update(quantity: quantity)
43
+ update(quantity: quantity)
44
44
 
45
- self.subscription.update(quantity: quantity) if self.subscription.has_single_plan
45
+ subscription.update(quantity: quantity) if subscription.single_plan?
46
46
 
47
47
  self
48
48
  end
49
49
 
50
50
  # Swap the subscription item to a new Stripe plan.
51
51
  def swap(plan, options = {})
52
- self.subscription.guard_against_incomplete
52
+ subscription.guard_against_incomplete
53
53
 
54
54
  options = {
55
- :plan => plan,
56
- :quantity => self.quantity,
57
- :payment_behavior => self.payment_behavior,
58
- :proration_behavior => self.prorate_behavior,
59
- :tax_rates => self.subscription.get_plan_tax_rates_for_payload(plan)
55
+ plan: plan,
56
+ quantity: quantity,
57
+ payment_behavior: payment_behavior,
58
+ proration_behavior: proration_behavior,
59
+ tax_rates: subscription.get_plan_tax_rates_for_payload(plan),
60
60
  }.merge(options)
61
61
 
62
- item = Stripe::SubscriptionItem::update(
63
- self.stripe_id,
62
+ item = Stripe::SubscriptionItem.update(
63
+ stripe_id,
64
64
  options,
65
- self.subscription.owner.stripe_options
65
+ subscription.owner.stripe_options
66
66
  )
67
67
 
68
- self.update(stripe_plan: plan, quantity: item.quantity)
68
+ update(stripe_plan: plan, quantity: item.quantity)
69
69
 
70
- self.subscription.update(stripe_plan: plan, quantity: item.quantity) if self.subscription.has_single_plan
70
+ subscription.update(stripe_plan: plan, quantity: item.quantity) if subscription.single_plan?
71
71
 
72
72
  self
73
73
  end
74
74
 
75
75
  # Swap the subscription item to a new Stripe plan, and invoice immediately.
76
76
  def swap_and_invoice(plan, options = {})
77
- self.always_invoice
77
+ always_invoice
78
78
 
79
- self.swap(plan, options)
79
+ swap(plan, options)
80
80
  end
81
81
 
82
82
  # Update the underlying Stripe subscription item information for the model.
83
83
  def update_stripe_subscription_item(options = {})
84
84
  Stripe::SubscriptionItem.update(
85
- self.stripe_id, options, self.subscription.owner.stripe_options
85
+ stripe_id, options, subscription.owner.stripe_options
86
86
  )
87
87
  end
88
88
 
89
89
  # Get the subscription as a Stripe subscription item object.
90
90
  def as_stripe_subscription_item(expand = {})
91
91
  Stripe::SubscriptionItem.retrieve(
92
- {:id => self.stripe_id, :expand => expand},
93
- self.subscription.owner.stripe_options
92
+ { id: stripe_id, expand: expand },
93
+ subscription.owner.stripe_options
94
94
  )
95
95
  end
96
96
  end
@@ -9,13 +9,11 @@ module Reji
9
9
  end
10
10
 
11
11
  # Get the applied currency.
12
- def currency
13
- @currency
14
- end
12
+ attr_reader :currency
15
13
 
16
14
  # Get the total tax that was paid (or will be paid).
17
15
  def amount
18
- self.format_amount(@amount)
16
+ format_amount(@amount)
19
17
  end
20
18
 
21
19
  # Get the raw total tax that was paid (or will be paid).
@@ -24,24 +22,24 @@ module Reji
24
22
  end
25
23
 
26
24
  # Determine if the tax is inclusive or not.
27
- def is_inclusive
25
+ def inclusive?
28
26
  @tax_rate.inclusive
29
27
  end
30
28
 
31
29
  # Stripe::TaxRate
32
- def tax_rate
33
- @tax_rate
34
- end
30
+ attr_reader :tax_rate
35
31
 
36
32
  # Dynamically get values from the Stripe TaxRate.
37
33
  def method_missing(key)
38
34
  @tax_rate[key]
39
35
  end
40
36
 
41
- protected
37
+ def respond_to_missing?(method_name, include_private = false)
38
+ super
39
+ end
42
40
 
43
41
  # Format the given amount into a displayable currency.
44
- def format_amount(amount)
42
+ protected def format_amount(amount)
45
43
  Reji.format_amount(amount, @currency)
46
44
  end
47
45
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reji
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- $:.push File.expand_path('lib', __dir__)
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
4
 
5
5
  require 'reji/version'
6
6
 
@@ -20,13 +20,14 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ['lib']
21
21
  s.test_files = `git ls-files -- {spec}/*`.split("\n")
22
22
 
23
- s.add_dependency 'stripe', '>= 5.0'
23
+ s.add_dependency 'actionmailer', '>= 5.0'
24
+ s.add_dependency 'activerecord', '>= 5.0'
24
25
  s.add_dependency 'money', '>= 6.0'
25
26
  s.add_dependency 'railties', '>= 5.0'
26
- s.add_dependency 'activerecord', '>= 5.0'
27
- s.add_dependency 'actionmailer', '>= 5.0'
27
+ s.add_dependency 'stripe', '>= 5.0'
28
28
  s.add_dependency 'wicked_pdf'
29
29
  s.add_dependency 'wkhtmltopdf-binary'
30
+ s.add_development_dependency 'appraisal'
30
31
  s.add_development_dependency 'rspec-rails', '~> 4.0.1'
31
32
  s.add_development_dependency 'sqlite3', '~> 1.4.2'
32
33
  end
@@ -4,18 +4,14 @@ class User < ActiveRecord::Base
4
4
  include Reji::Billable
5
5
 
6
6
  def tax_rates
7
- @tax_rates || {}
7
+ @tax_rates || []
8
8
  end
9
9
 
10
10
  def plan_tax_rates
11
11
  @plan_tax_rates || {}
12
12
  end
13
13
 
14
- def plan_tax_rates=(value)
15
- @plan_tax_rates = value
16
- end
14
+ attr_writer :plan_tax_rates
17
15
 
18
- def tax_rates=(value)
19
- @tax_rates = value
20
- end
16
+ attr_writer :tax_rates
21
17
  end
@@ -4,7 +4,7 @@ require 'rails/all'
4
4
  require 'reji'
5
5
 
6
6
  module Dummy
7
- APP_ROOT = File.expand_path('..', __FILE__).freeze
7
+ APP_ROOT = File.expand_path(__dir__).freeze
8
8
 
9
9
  I18n.enforce_available_locales = true
10
10
 
@@ -30,14 +30,10 @@ module Dummy
30
30
  config.secret_key_base = 'SECRET_KEY_BASE'
31
31
 
32
32
  if config.active_record.sqlite3.respond_to?(:represent_boolean_as_integer)
33
- if Rails::VERSION::MAJOR < 6
34
- config.active_record.sqlite3.represent_boolean_as_integer = true
35
- end
33
+ config.active_record.sqlite3.represent_boolean_as_integer = true if Rails::VERSION::MAJOR < 6
36
34
  end
37
35
 
38
- if Rails::VERSION::MAJOR >= 6
39
- config.action_mailer.delivery_job = 'ActionMailer::MailDeliveryJob'
40
- end
36
+ config.action_mailer.delivery_job = 'ActionMailer::MailDeliveryJob' if Rails::VERSION::MAJOR >= 6
41
37
 
42
38
  config.active_job.queue_adapter = :inline
43
39
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ActiveRecord::Schema.define(version: 2020_01_01_00_00_00) do
3
+ ActiveRecord::Schema.define do
4
4
  create_table 'subscription_items', force: true do |t|
5
5
  t.bigint 'subscription_id', null: false
6
6
  t.string 'stripe_id', null: false
@@ -9,7 +9,7 @@ ActiveRecord::Schema.define(version: 2020_01_01_00_00_00) do
9
9
  t.datetime 'created_at', precision: 6, null: false
10
10
  t.datetime 'updated_at', precision: 6, null: false
11
11
  t.index ['stripe_id'], name: 'index_subscription_items_on_stripe_id'
12
- t.index ['subscription_id', 'stripe_plan'], name: 'index_subscription_items_on_subscription_id_and_stripe_plan', unique: true
12
+ t.index %w[subscription_id stripe_plan], name: 'index_subscription_items_on_subscription_id_and_stripe_plan', unique: true
13
13
  end
14
14
 
15
15
  create_table 'subscriptions', force: true do |t|
@@ -23,7 +23,7 @@ ActiveRecord::Schema.define(version: 2020_01_01_00_00_00) do
23
23
  t.timestamp 'ends_at'
24
24
  t.datetime 'created_at', precision: 6, null: false
25
25
  t.datetime 'updated_at', precision: 6, null: false
26
- t.index ['user_id', 'stripe_status'], name: 'index_subscriptions_on_user_id_and_stripe_status'
26
+ t.index %w[user_id stripe_status], name: 'index_subscriptions_on_user_id_and_stripe_status'
27
27
  end
28
28
 
29
29
  create_table 'users', force: true do |t|
@@ -37,4 +37,3 @@ ActiveRecord::Schema.define(version: 2020_01_01_00_00_00) do
37
37
  t.index ['email'], name: 'index_users_on_email', unique: true
38
38
  end
39
39
  end
40
-