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
@@ -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