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
@@ -34,7 +34,7 @@
34
34
  </p>
35
35
 
36
36
  <div class="bg-white rounded-lg shadow-xl p-4 sm:py-6 sm:px-10 mb-5">
37
- <% if @payment.is_succeeded %>
37
+ <% if @payment.succeeded? %>
38
38
  <h1 class="text-xl mt-2 mb-4 text-gray-700">
39
39
  Payment Successful
40
40
  </h1>
@@ -42,7 +42,7 @@
42
42
  <p class="mb-6">
43
43
  This payment was already successfully confirmed.
44
44
  </p>
45
- <% elsif @payment.is_cancelleed %>
45
+ <% elsif @payment.cancelled? %>
46
46
  <h1 class="text-xl mt-2 mb-4 text-gray-700">
47
47
  Payment Cancelled
48
48
  </h1>
@@ -104,7 +104,7 @@
104
104
  </div>
105
105
 
106
106
  <p class="text-center text-gray-500 text-sm">
107
- © <%= Time.now.year %>. All rights reserved.
107
+ © <%= Time.current.year %>. All rights reserved.
108
108
  </p>
109
109
  </div>
110
110
  </div>
@@ -126,7 +126,7 @@
126
126
  errorMessage: ''
127
127
  },
128
128
 
129
- <% if ! @payment.is_succeeded && ! @payment.is_cancelleed && ! @payment.requires_action %>
129
+ <% if ! @payment.succeeded? && ! @payment.cancelled? && ! @payment.requires_action %>
130
130
  mounted: function () {
131
131
  this.configureStripe();
132
132
  },
@@ -118,7 +118,7 @@
118
118
  <th align="left">Description</th>
119
119
  <th align="right">Date</th>
120
120
 
121
- <% if invoice.has_tax %>
121
+ <% if invoice.tax? %>
122
122
  <th align="right">Tax</th>
123
123
  <% end %>
124
124
 
@@ -129,13 +129,13 @@
129
129
  <% invoice.invoice_items.each do |item| %>
130
130
  <tr class="row">
131
131
  <td colspan="2"><%= item.description %></td>
132
- <% if invoice.has_tax %>
132
+ <% if invoice.tax? %>
133
133
  <td>
134
134
  <% if item.inclusive_tax_percentage > 0 %>
135
135
  <%= item.inclusive_tax_percentage %>% incl.
136
136
  <% end %>
137
137
 
138
- <% if item.has_both_inclusive_and_exclusive_tax > 0 %>
138
+ <% if item.both_inclusive_and_exclusive_tax? %>
139
139
  +
140
140
  <% end %>
141
141
 
@@ -156,13 +156,13 @@
156
156
  <%= subscription.start_date %>
157
157
  <%= subscription.end_date %>
158
158
  </td>
159
- <% if invoice.has_tax %>
159
+ <% if invoice.tax? %>
160
160
  <td>
161
161
  <% if subscription.inclusive_tax_percentage > 0 %>
162
162
  <%= subscription.inclusive_tax_percentage %>% incl.
163
163
  <% end %>
164
164
 
165
- <% if subscription.has_both_inclusive_and_exclusive_tax > 0 %>
165
+ <% if subscription.both_inclusive_and_exclusive_tax? %>
166
166
  +
167
167
  <% end %>
168
168
 
@@ -176,17 +176,17 @@
176
176
  <% end %>
177
177
 
178
178
  <!-- Display The Subtotal -->
179
- <% if invoice.has_discount || invoice.has_tax || invoice.has_starting_balance %>
179
+ <% if invoice.discount? || invoice.tax? || invoice.starting_balance? %>
180
180
  <tr>
181
- <td colspan="<%= invoice.has_tax ? 3 : 2 %>" style="text-align: right;">Subtotal</td>
181
+ <td colspan="<%= invoice.tax? ? 3 : 2 %>" style="text-align: right;">Subtotal</td>
182
182
  <td><%= invoice.subtotal %></td>
183
183
  </tr>
184
184
  <% end %>
185
185
 
186
186
  <!-- Display The Discount -->
187
- <% if invoice.has_discount %>
187
+ <% if invoice.discount? %>
188
188
  <tr>
189
- <td colspan="<%= invoice.has_tax ? 3 : 2 %>" style="text-align: right;">
189
+ <td colspan="<%= invoice.tax? ? 3 : 2 %>" style="text-align: right;">
190
190
  <% if invoice.discount_is_percentage %>
191
191
  <%= invoice.coupon %> (<%= invoice.percent_off %>% Off)
192
192
  <% else %>
@@ -199,10 +199,10 @@
199
199
  <% end %>
200
200
 
201
201
  <!-- Display The Taxes -->
202
- <% unless invoice.is_not_tax_exempt %>
202
+ <% unless invoice.not_tax_exempt? %>
203
203
  <tr>
204
- <td colspan="<%= invoice.has_tax ? 3 : 2 %>" style="text-align: right;">
205
- <% if invoice.is_tax_exempt %>
204
+ <td colspan="<%= invoice.tax? ? 3 : 2 %>" style="text-align: right;">
205
+ <% if invoice.tax_exempt? %>
206
206
  Tax is exempted
207
207
  <% else %>
208
208
  Tax to be paid on reverse charge basis
@@ -215,7 +215,7 @@
215
215
  <tr>
216
216
  <td colspan="3" style="text-align: right;">
217
217
  <%= tax.display_name %> <%= tax.jurisdiction ? ' - '.tax.jurisdiction : '' %>
218
- (<%= tax.percentage %>%<%= tax.is_inclusive ? ' incl.' : '' %>)
218
+ (<%= tax.percentage %>%<%= tax.inclusive? ? ' incl.' : '' %>)
219
219
  </td>
220
220
  <td><%= tax.amount %></td>
221
221
  </tr>
@@ -223,9 +223,9 @@
223
223
  <% end %>
224
224
 
225
225
  <!-- Starting Balance -->
226
- <% if invoice.has_starting_balance %>
226
+ <% if invoice.starting_balance? %>
227
227
  <tr>
228
- <td colspan="<%= invoice.has_tax ? 3 : 2 %>" style="text-align: right;">
228
+ <td colspan="<%= invoice.tax? ? 3 : 2 %>" style="text-align: right;">
229
229
  Customer Balance
230
230
  </td>
231
231
  <td><%= invoice.starting_balance %></td>
@@ -234,7 +234,7 @@
234
234
 
235
235
  <!-- Display The Final Total -->
236
236
  <tr>
237
- <td colspan="<%= invoice.has_tax ? 3 : 2 %>" style="text-align: right;">
237
+ <td colspan="<%= invoice.tax? ? 3 : 2 %>" style="text-align: right;">
238
238
  <strong>Total</strong>
239
239
  </td>
240
240
  <td>
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Rails.application.routes.draw do
2
4
  scope 'stripe', as: 'stripe' do
3
5
  get 'payment/:id', to: 'reji/payment#show', as: 'payment'
@@ -1,13 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file was generated by Appraisal
2
4
 
3
- source "https://rubygems.org"
5
+ source 'https://rubygems.org'
4
6
 
5
- gem "appraisal"
6
- gem "money"
7
- gem "pry", require: false
8
- gem "rspec-rails", "~> 3.1"
9
- gem "stripe"
10
- gem "sqlite3", "~> 1.3.13"
11
- gem "railties", "~> 5.0"
7
+ gem 'pry', require: false
8
+ gem 'railties', '~> 5.0'
9
+ gem 'rspec-rails', '~> 3.1'
10
+ gem 'rubocop-rails', require: false
11
+ gem 'sqlite3', '~> 1.3.13'
12
12
 
13
- gemspec path: "../"
13
+ gemspec path: '../'
@@ -1,13 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file was generated by Appraisal
2
4
 
3
- source "https://rubygems.org"
5
+ source 'https://rubygems.org'
4
6
 
5
- gem "appraisal"
6
- gem "money"
7
- gem "pry", require: false
8
- gem "rspec-rails"
9
- gem "stripe"
10
- gem "sqlite3"
11
- gem "railties", "~> 5.1"
7
+ gem 'pry', require: false
8
+ gem 'railties', '~> 5.1'
9
+ gem 'rubocop-rails', require: false
12
10
 
13
- gemspec path: "../"
11
+ gemspec path: '../'
@@ -1,13 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file was generated by Appraisal
2
4
 
3
- source "https://rubygems.org"
5
+ source 'https://rubygems.org'
4
6
 
5
- gem "appraisal"
6
- gem "money"
7
- gem "pry", require: false
8
- gem "rspec-rails"
9
- gem "stripe"
10
- gem "sqlite3"
11
- gem "railties", "~> 5.2"
7
+ gem 'pry', require: false
8
+ gem 'railties', '~> 5.2'
9
+ gem 'rubocop-rails', require: false
12
10
 
13
- gemspec path: "../"
11
+ gemspec path: '../'
@@ -1,13 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file was generated by Appraisal
2
4
 
3
- source "https://rubygems.org"
5
+ source 'https://rubygems.org'
4
6
 
5
- gem "appraisal"
6
- gem "money"
7
- gem "pry", require: false
8
- gem "rspec-rails"
9
- gem "stripe"
10
- gem "sqlite3"
11
- gem "railties", "~> 6.0"
7
+ gem 'pry', require: false
8
+ gem 'railties', '~> 6.0'
9
+ gem 'rubocop-rails', require: false
12
10
 
13
- gemspec path: "../"
11
+ gemspec path: '../'
@@ -8,7 +8,7 @@ module Reji
8
8
  class InstallGenerator < Rails::Generators::Base
9
9
  include Rails::Generators::Migration
10
10
 
11
- source_root(File.expand_path('../templates', __FILE__))
11
+ source_root(File.expand_path('templates', __dir__))
12
12
 
13
13
  def create_reji_initializer
14
14
  copy_file('reji.rb', 'config/initializers/reji.rb')
@@ -20,48 +20,44 @@ module Reji
20
20
  copy_migration('create_subscription_items')
21
21
  end
22
22
 
23
- private
23
+ # for generating a timestamp when using `create_migration`
24
+ def self.next_migration_number(dir)
25
+ ActiveRecord::Generators::Base.next_migration_number(dir)
26
+ end
24
27
 
25
- def copy_migration(migration_name, config = {})
26
- unless migration_exists?(migration_name)
27
- migration_template(
28
- "db/migrate/#{migration_name}.rb.erb",
29
- "db/migrate/#{migration_name}.rb",
30
- config.merge(migration_version: migration_version),
31
- )
32
- end
28
+ private def copy_migration(migration_name, config = {})
29
+ return if migration_exists?(migration_name)
30
+
31
+ migration_template(
32
+ "db/migrate/#{migration_name}.rb.erb",
33
+ "db/migrate/#{migration_name}.rb",
34
+ config.merge(migration_version: migration_version)
35
+ )
33
36
  end
34
37
 
35
- def migration_exists?(name)
38
+ private def migration_exists?(name)
36
39
  existing_migrations.include?(name)
37
40
  end
38
41
 
39
- def existing_migrations
42
+ private def existing_migrations
40
43
  @existing_migrations ||= Dir.glob('db/migrate/*.rb').map do |file|
41
44
  migration_name_without_timestamp(file)
42
45
  end
43
46
  end
44
47
 
45
- def migration_name_without_timestamp(file)
48
+ private def migration_name_without_timestamp(file)
46
49
  file.sub(%r{^.*(db/migrate/)(?:\d+_)?}, '')
47
50
  end
48
51
 
49
- # for generating a timestamp when using `create_migration`
50
- def self.next_migration_number(dir)
51
- ActiveRecord::Generators::Base.next_migration_number(dir)
52
- end
53
-
54
- def migration_version
52
+ private def migration_version
55
53
  "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
56
54
  end
57
55
 
58
- def migration_primary_key_type_string
59
- if configured_key_type
60
- ", id: :#{configured_key_type}"
61
- end
56
+ private def migration_primary_key_type_string
57
+ ", id: :#{configured_key_type}" if configured_key_type
62
58
  end
63
59
 
64
- def configured_key_type
60
+ private def configured_key_type
65
61
  Rails.configuration.generators.active_record[:primary_key_type]
66
62
  end
67
63
  end
@@ -15,8 +15,8 @@ Reji.configure do |config|
15
15
  # your Stripe webhook handling controllers. The tolerance setting will
16
16
  # check the drift between the current time and the signed request's.
17
17
  config.webhook = {
18
- :secret => ENV['STRIPE_WEBHOOK_SECRET'],
19
- :tolerance => ENV['STRIPE_WEBHOOK_TOLERANCE'] || 300,
18
+ secret: ENV['STRIPE_WEBHOOK_SECRET'],
19
+ tolerance: ENV['STRIPE_WEBHOOK_TOLERANCE'] || 300,
20
20
  }
21
21
 
22
22
  # Reji Model
@@ -30,11 +30,7 @@ module Reji
30
30
  STRIPE_VERSION = '2020-08-27'
31
31
 
32
32
  # Indicates if Reji will mark past due subscriptions as inactive.
33
- @@deactivate_past_due = true
34
-
35
- class << self
36
- attr_accessor :deactivate_past_due
37
- end
33
+ @deactivate_past_due = true
38
34
 
39
35
  # Get the billable entity instance by Stripe ID.
40
36
  def self.find_billable(stripe_id)
@@ -47,8 +43,8 @@ module Reji
47
43
  # Get the default Stripe API options.
48
44
  def self.stripe_options(options = {})
49
45
  {
50
- :api_key => Reji.configuration.secret,
51
- :stripe_version => Reji::STRIPE_VERSION,
46
+ api_key: Reji.configuration.secret,
47
+ stripe_version: Reji::STRIPE_VERSION,
52
48
  }.merge(options)
53
49
  end
54
50
 
@@ -66,10 +62,18 @@ module Reji
66
62
 
67
63
  # Configure to maintain past due subscriptions as active.
68
64
  def self.keep_past_due_subscriptions_active
69
- @@deactivate_past_due = false
65
+ @deactivate_past_due = false
70
66
 
71
67
  self
72
68
  end
69
+
70
+ def self.deactivate_past_due=(value)
71
+ @deactivate_past_due = value
72
+ end
73
+
74
+ def self.deactivate_past_due
75
+ @deactivate_past_due
76
+ end
73
77
  end
74
78
 
75
79
  Stripe.set_app_info('Rails Reji')
@@ -5,26 +5,24 @@ module Reji
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  # Determine if the entity has a Stripe customer ID.
8
- def has_stripe_id
9
- ! self.stripe_id.nil?
8
+ def stripe_id?
9
+ !stripe_id.nil?
10
10
  end
11
11
 
12
12
  # Create a Stripe customer for the given model.
13
13
  def create_as_stripe_customer(options = {})
14
- raise Reji::CustomerAlreadyCreatedError.exists(self) if self.has_stripe_id
14
+ raise Reji::CustomerAlreadyCreatedError.exists(self) if stripe_id?
15
15
 
16
- if ! options.key?('email') && self.stripe_email
17
- options[:email] = self.stripe_email
18
- end
16
+ options[:email] = stripe_email if !options.key?('email') && stripe_email
19
17
 
20
18
  # Here we will create the customer instance on Stripe and store the ID of the
21
19
  # user from Stripe. This ID will correspond with the Stripe user instances
22
20
  # and allow us to retrieve users from Stripe later when we need to work.
23
21
  customer = Stripe::Customer.create(
24
- options, self.stripe_options
22
+ options, stripe_options
25
23
  )
26
24
 
27
- self.update({:stripe_id => customer.id})
25
+ update({ stripe_id: customer.id })
28
26
 
29
27
  customer
30
28
  end
@@ -32,34 +30,34 @@ module Reji
32
30
  # Update the underlying Stripe customer information for the model.
33
31
  def update_stripe_customer(options = {})
34
32
  Stripe::Customer.update(
35
- self.stripe_id, options, self.stripe_options
33
+ stripe_id, options, stripe_options
36
34
  )
37
35
  end
38
36
 
39
37
  # Get the Stripe customer instance for the current user or create one.
40
38
  def create_or_get_stripe_customer(options = {})
41
- return self.as_stripe_customer if self.has_stripe_id
39
+ return as_stripe_customer if stripe_id?
42
40
 
43
- self.create_as_stripe_customer(options)
41
+ create_as_stripe_customer(options)
44
42
  end
45
43
 
46
44
  # Get the Stripe customer for the model.
47
45
  def as_stripe_customer
48
- self.assert_customer_exists
46
+ assert_customer_exists
49
47
 
50
- Stripe::Customer.retrieve(self.stripe_id, self.stripe_options)
48
+ Stripe::Customer.retrieve(stripe_id, stripe_options)
51
49
  end
52
50
 
53
51
  # Get the email address used to create the customer in Stripe.
54
52
  def stripe_email
55
- self.email
53
+ email
56
54
  end
57
55
 
58
56
  # Apply a coupon to the billable entity.
59
57
  def apply_coupon(coupon)
60
- self.assert_customer_exists
58
+ assert_customer_exists
61
59
 
62
- customer = self.as_stripe_customer
60
+ customer = as_stripe_customer
63
61
 
64
62
  customer.coupon = coupon
65
63
 
@@ -73,29 +71,29 @@ module Reji
73
71
 
74
72
  # Get the Stripe billing portal for this customer.
75
73
  def billing_portal_url(return_url = nil)
76
- self.assert_customer_exists
74
+ assert_customer_exists
77
75
 
78
76
  session = Stripe::BillingPortal::Session.create({
79
- :customer => self.stripe_id,
80
- :return_url => return_url || '/',
81
- }, self.stripe_options)
77
+ customer: stripe_id,
78
+ return_url: return_url || '/',
79
+ }, stripe_options)
82
80
 
83
81
  session.url
84
82
  end
85
83
 
86
84
  # Determine if the customer is not exempted from taxes.
87
- def is_not_tax_exempt
88
- self.as_stripe_customer.tax_exempt == 'none'
85
+ def not_tax_exempt?
86
+ as_stripe_customer.tax_exempt == 'none'
89
87
  end
90
88
 
91
89
  # Determine if the customer is exempted from taxes.
92
- def is_tax_exempt
93
- self.as_stripe_customer.tax_exempt == 'exempt'
90
+ def tax_exempt?
91
+ as_stripe_customer.tax_exempt == 'exempt'
94
92
  end
95
93
 
96
94
  # Determine if reverse charge applies to the customer.
97
95
  def reverse_charge_applies
98
- self.as_stripe_customer.tax_exempt == 'reverse'
96
+ as_stripe_customer.tax_exempt == 'reverse'
99
97
  end
100
98
 
101
99
  # Get the default Stripe API options for the current Billable model.
@@ -103,11 +101,9 @@ module Reji
103
101
  Reji.stripe_options(options)
104
102
  end
105
103
 
106
- protected
107
-
108
104
  # Determine if the entity has a Stripe customer ID and throw an exception if not.
109
- def assert_customer_exists
110
- raise Reji::InvalidCustomerError.not_yet_created(self) unless self.has_stripe_id
105
+ protected def assert_customer_exists
106
+ raise Reji::InvalidCustomerError.not_yet_created(self) unless stripe_id?
111
107
  end
112
108
  end
113
109
  end