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
@@ -6,64 +6,61 @@ module Reji
6
6
 
7
7
  # Add an invoice item to the customer's upcoming invoice.
8
8
  def tab(description, amount, options = {})
9
- self.assert_customer_exists
9
+ assert_customer_exists
10
10
 
11
11
  options = {
12
- :customer => self.stripe_id,
13
- :amount => amount,
14
- :currency => self.preferred_currency,
15
- :description => description,
12
+ customer: stripe_id,
13
+ amount: amount,
14
+ currency: preferred_currency,
15
+ description: description,
16
16
  }.merge(options)
17
17
 
18
- Stripe::InvoiceItem.create(options, self.stripe_options)
18
+ Stripe::InvoiceItem.create(options, stripe_options)
19
19
  end
20
20
 
21
21
  # Invoice the customer for the given amount and generate an invoice immediately.
22
22
  def invoice_for(description, amount, tab_options = {}, invoice_options = {})
23
- self.tab(description, amount, tab_options)
23
+ tab(description, amount, tab_options)
24
24
 
25
- self.invoice(invoice_options)
25
+ invoice(invoice_options)
26
26
  end
27
27
 
28
28
  # Invoice the billable entity outside of the regular billing cycle.
29
29
  def invoice(options = {})
30
- self.assert_customer_exists
31
-
32
- parameters = options.merge({:customer => self.stripe_id})
30
+ assert_customer_exists
33
31
 
34
32
  begin
35
- stripe_invoice = Stripe::Invoice.create(parameters, self.stripe_options)
33
+ stripe_invoice = Stripe::Invoice.create(options.merge({ customer: stripe_id }), stripe_options)
36
34
 
37
- if stripe_invoice.collection_method == 'charge_automatically'
38
- stripe_invoice = stripe_invoice.pay
39
- else
40
- stripe_invoice = stripe_invoice.send_invoice
41
- end
35
+ stripe_invoice =
36
+ if stripe_invoice.collection_method == 'charge_automatically'
37
+ stripe_invoice.pay
38
+ else
39
+ stripe_invoice.send_invoice
40
+ end
42
41
 
43
42
  Invoice.new(self, stripe_invoice)
44
- rescue Stripe::InvalidRequestError => e
43
+ rescue Stripe::InvalidRequestError => _e
45
44
  false
46
- rescue Stripe::CardError => e
47
- payment = Payment.new(
45
+ rescue Stripe::CardError => _e
46
+ Payment.new(
48
47
  Stripe::PaymentIntent.retrieve(
49
- {:id => stripe_invoice.payment_intent, :expand => ['invoice.subscription']},
50
- self.stripe_options
48
+ { id: stripe_invoice.payment_intent, expand: ['invoice.subscription'] },
49
+ stripe_options
51
50
  )
52
- )
53
-
54
- payment.validate
51
+ ).validate
55
52
  end
56
53
  end
57
54
 
58
55
  # Get the entity's upcoming invoice.
59
56
  def upcoming_invoice
60
- return unless self.has_stripe_id
57
+ return unless stripe_id?
61
58
 
62
59
  begin
63
- stripe_invoice = Stripe::Invoice.upcoming({:customer => self.stripe_id}, self.stripe_options)
60
+ stripe_invoice = Stripe::Invoice.upcoming({ customer: stripe_id }, stripe_options)
64
61
 
65
62
  Invoice.new(self, stripe_invoice)
66
- rescue Stripe::InvalidRequestError => e
63
+ rescue Stripe::InvalidRequestError => _e
67
64
  #
68
65
  end
69
66
  end
@@ -73,8 +70,8 @@ module Reji
73
70
  stripe_invoice = nil
74
71
 
75
72
  begin
76
- stripe_invoice = Stripe::Invoice.retrieve(id, self.stripe_options)
77
- rescue => e
73
+ stripe_invoice = Stripe::Invoice.retrieve(id, stripe_options)
74
+ rescue StandardError => _e
78
75
  #
79
76
  end
80
77
 
@@ -84,9 +81,9 @@ module Reji
84
81
  # Find an invoice or throw a 404 or 403 error.
85
82
  def find_invoice_or_fail(id)
86
83
  begin
87
- invoice = self.find_invoice(id)
84
+ invoice = find_invoice(id)
88
85
  rescue InvalidInvoiceError => e
89
- raise Reji::AccessDeniedHttpError.new(e.message)
86
+ raise Reji::AccessDeniedHttpError, e.message
90
87
  end
91
88
 
92
89
  raise ActiveRecord::RecordNotFound if invoice.nil?
@@ -96,33 +93,29 @@ module Reji
96
93
 
97
94
  # Create an invoice download response.
98
95
  def download_invoice(id, data, filename = nil)
99
- invoice = self.find_invoice_or_fail(id)
96
+ invoice = find_invoice_or_fail(id)
100
97
 
101
98
  filename ? invoice.download_as(filename, data) : invoice.download(data)
102
99
  end
103
100
 
104
101
  # Get a collection of the entity's invoices.
105
102
  def invoices(include_pending = false, parameters = {})
106
- return [] unless self.has_stripe_id
103
+ return [] unless stripe_id?
107
104
 
108
105
  invoices = []
109
106
 
110
- parameters = {:limit => 24}.merge(parameters)
107
+ parameters = { limit: 24 }.merge(parameters)
111
108
 
112
109
  stripe_invoices = Stripe::Invoice.list(
113
- {:customer => self.stripe_id}.merge(parameters),
114
- self.stripe_options
110
+ { customer: stripe_id }.merge(parameters),
111
+ stripe_options
115
112
  )
116
113
 
117
114
  # Here we will loop through the Stripe invoices and create our own custom Invoice
118
115
  # instances that have more helper methods and are generally more convenient to
119
116
  # work with than the plain Stripe objects are. Then, we'll return the array.
120
- unless stripe_invoices.nil?
121
- stripe_invoices.data.each do |invoice|
122
- if invoice.paid || include_pending
123
- invoices << Invoice.new(self, invoice)
124
- end
125
- end
117
+ stripe_invoices&.data&.each do |invoice|
118
+ invoices << Invoice.new(self, invoice) if invoice.paid || include_pending
126
119
  end
127
120
 
128
121
  invoices
@@ -130,7 +123,7 @@ module Reji
130
123
 
131
124
  # Get an array of the entity's invoices.
132
125
  def invoices_include_pending(parameters = {})
133
- self.invoices(true, parameters)
126
+ invoices(true, parameters)
134
127
  end
135
128
  end
136
129
  end
@@ -6,29 +6,29 @@ module Reji
6
6
 
7
7
  # Create a new SetupIntent instance.
8
8
  def create_setup_intent(options = {})
9
- Stripe::SetupIntent.create(options, self.stripe_options)
9
+ Stripe::SetupIntent.create(options, stripe_options)
10
10
  end
11
11
 
12
12
  # Determines if the customer currently has a default payment method.
13
- def has_default_payment_method
14
- ! self.card_brand.blank?
13
+ def default_payment_method?
14
+ card_brand.present?
15
15
  end
16
16
 
17
17
  # Determines if the customer currently has at least one payment method.
18
- def has_payment_method
19
- ! self.payment_methods.empty?
18
+ def payment_method?
19
+ !payment_methods.empty?
20
20
  end
21
21
 
22
22
  # Get a collection of the entity's payment methods.
23
23
  def payment_methods(parameters = {})
24
- return [] unless self.has_stripe_id
24
+ return [] unless stripe_id?
25
25
 
26
- parameters = {:limit => 24}.merge(parameters)
26
+ parameters = { limit: 24 }.merge(parameters)
27
27
 
28
28
  # "type" is temporarily required by Stripe...
29
29
  payment_methods = Stripe::PaymentMethod.list(
30
- {customer: self.stripe_id, type: 'card'}.merge(parameters),
31
- self.stripe_options
30
+ { customer: stripe_id, type: 'card' }.merge(parameters),
31
+ stripe_options
32
32
  )
33
33
 
34
34
  payment_methods.data.map { |payment_method| PaymentMethod.new(self, payment_method) }
@@ -36,13 +36,13 @@ module Reji
36
36
 
37
37
  # Add a payment method to the customer.
38
38
  def add_payment_method(payment_method)
39
- self.assert_customer_exists
39
+ assert_customer_exists
40
40
 
41
- stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
41
+ stripe_payment_method = resolve_stripe_payment_method(payment_method)
42
42
 
43
- if stripe_payment_method.customer != self.stripe_id
43
+ if stripe_payment_method.customer != stripe_id
44
44
  stripe_payment_method = stripe_payment_method.attach(
45
- {:customer => self.stripe_id}, self.stripe_options
45
+ { customer: stripe_id }, stripe_options
46
46
  )
47
47
  end
48
48
 
@@ -51,74 +51,64 @@ module Reji
51
51
 
52
52
  # Remove a payment method from the customer.
53
53
  def remove_payment_method(payment_method)
54
- self.assert_customer_exists
54
+ assert_customer_exists
55
55
 
56
- stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
56
+ stripe_payment_method = resolve_stripe_payment_method(payment_method)
57
57
 
58
- return if stripe_payment_method.customer != self.stripe_id
58
+ return if stripe_payment_method.customer != stripe_id
59
59
 
60
- customer = self.as_stripe_customer
60
+ customer = as_stripe_customer
61
61
 
62
62
  default_payment_method = customer.invoice_settings.default_payment_method
63
63
 
64
- stripe_payment_method.detach({}, self.stripe_options)
64
+ stripe_payment_method.detach({}, stripe_options)
65
65
 
66
66
  # If the payment method was the default payment method, we'll remove it manually...
67
- if stripe_payment_method.id == default_payment_method
68
- self.update({
69
- :card_brand => nil,
70
- :card_last_four => nil,
71
- })
72
- end
67
+ update({ card_brand: nil, card_last_four: nil }) if stripe_payment_method.id == default_payment_method
73
68
  end
74
69
 
75
70
  # Get the default payment method for the entity.
76
71
  def default_payment_method
77
- return unless self.has_stripe_id
72
+ return unless stripe_id?
78
73
 
79
74
  customer = Stripe::Customer.retrieve({
80
- :id => self.stripe_id,
81
- :expand => [
75
+ id: stripe_id,
76
+ expand: [
82
77
  'invoice_settings.default_payment_method',
83
78
  'default_source',
84
- ]
85
- }, self.stripe_options)
86
-
87
- if customer.invoice_settings.default_payment_method
88
- return PaymentMethod.new(
89
- self,
90
- customer.invoice_settings.default_payment_method
91
- )
92
- end
79
+ ],
80
+ }, stripe_options)
93
81
 
94
82
  # If we can't find a payment method, try to return a legacy source...
95
- customer.default_source
83
+ return customer.default_source unless customer.invoice_settings.default_payment_method
84
+
85
+ PaymentMethod.new(self, customer.invoice_settings.default_payment_method)
96
86
  end
97
87
 
98
88
  # Update customer's default payment method.
99
89
  def update_default_payment_method(payment_method)
100
- self.assert_customer_exists
90
+ assert_customer_exists
101
91
 
102
- customer = self.as_stripe_customer
92
+ customer = as_stripe_customer
103
93
 
104
- stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
94
+ stripe_payment_method = resolve_stripe_payment_method(payment_method)
105
95
 
106
96
  # If the customer already has the payment method as their default, we can bail out
107
97
  # of the call now. We don't need to keep adding the same payment method to this
108
98
  # model's account every single time we go through this specific process call.
109
99
  return if stripe_payment_method.id == customer.invoice_settings.default_payment_method
110
100
 
111
- payment_method = self.add_payment_method(stripe_payment_method)
101
+ payment_method = add_payment_method(stripe_payment_method)
112
102
 
113
- customer.invoice_settings = {:default_payment_method => payment_method.id}
103
+ customer.invoice_settings = { default_payment_method: payment_method.id }
114
104
 
115
105
  customer.save
116
106
 
117
107
  # Next we will get the default payment method for this user so we can update the
118
108
  # payment method details on the record in the database. This will allow us to
119
109
  # show that information on the front-end when updating the payment methods.
120
- self.fill_payment_method_details(payment_method)
121
- self.save
110
+ fill_payment_method_details(payment_method)
111
+ save
122
112
 
123
113
  payment_method
124
114
  end
@@ -129,17 +119,12 @@ module Reji
129
119
 
130
120
  if default_payment_method
131
121
  if default_payment_method.instance_of? PaymentMethod
132
- self.fill_payment_method_details(
133
- default_payment_method.as_stripe_payment_method
134
- ).save
122
+ fill_payment_method_details(default_payment_method.as_stripe_payment_method).save
135
123
  else
136
- self.fill_source_details(default_payment_method).save
124
+ fill_source_details(default_payment_method).save
137
125
  end
138
126
  else
139
- self.update({
140
- :card_brand => nil,
141
- :card_last_four => nil,
142
- })
127
+ update({ card_brand: nil, card_last_four: nil })
143
128
  end
144
129
 
145
130
  self
@@ -147,9 +132,9 @@ module Reji
147
132
 
148
133
  # Deletes the entity's payment methods.
149
134
  def delete_payment_methods
150
- self.payment_methods.each { |payment_method| payment_method.delete }
135
+ payment_methods.each(&:delete)
151
136
 
152
- self.update_default_payment_method_from_stripe
137
+ update_default_payment_method_from_stripe
153
138
  end
154
139
 
155
140
  # Find a PaymentMethod by ID.
@@ -157,18 +142,16 @@ module Reji
157
142
  stripe_payment_method = nil
158
143
 
159
144
  begin
160
- stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
161
- rescue => e
145
+ stripe_payment_method = resolve_stripe_payment_method(payment_method)
146
+ rescue StandardError => _e
162
147
  #
163
148
  end
164
149
 
165
150
  stripe_payment_method ? PaymentMethod.new(self, stripe_payment_method) : nil
166
151
  end
167
152
 
168
- protected
169
-
170
153
  # Fills the model's properties with the payment method from Stripe.
171
- def fill_payment_method_details(payment_method)
154
+ protected def fill_payment_method_details(payment_method)
172
155
  if payment_method.type == 'card'
173
156
  self.card_brand = payment_method.card.brand
174
157
  self.card_last_four = payment_method.card.last4
@@ -178,7 +161,7 @@ module Reji
178
161
  end
179
162
 
180
163
  # Fills the model's properties with the source from Stripe.
181
- def fill_source_details(source)
164
+ protected def fill_source_details(source)
182
165
  if source.instance_of? Stripe::Card
183
166
  self.card_brand = source.brand
184
167
  self.card_last_four = source.last4
@@ -191,11 +174,11 @@ module Reji
191
174
  end
192
175
 
193
176
  # Resolve a PaymentMethod ID to a Stripe PaymentMethod object.
194
- def resolve_stripe_payment_method(payment_method)
177
+ protected def resolve_stripe_payment_method(payment_method)
195
178
  return payment_method if payment_method.instance_of? Stripe::PaymentMethod
196
179
 
197
180
  Stripe::PaymentMethod.retrieve(
198
- payment_method, self.stripe_options
181
+ payment_method, stripe_options
199
182
  )
200
183
  end
201
184
  end
@@ -15,54 +15,54 @@ module Reji
15
15
 
16
16
  # Determine if the Stripe model is on trial.
17
17
  def on_trial(name = 'default', plan = nil)
18
- return true if name == 'default' && plan.nil? && self.on_generic_trial
18
+ return true if name == 'default' && plan.nil? && on_generic_trial
19
19
 
20
20
  subscription = self.subscription(name)
21
21
 
22
- return false unless subscription && subscription.on_trial
22
+ return false unless subscription&.on_trial
23
23
 
24
- plan ? subscription.has_plan(plan) : true
24
+ plan ? subscription.plan?(plan) : true
25
25
  end
26
26
 
27
27
  # Determine if the Stripe model is on a "generic" trial at the model level.
28
28
  def on_generic_trial
29
- !! self.trial_ends_at && self.trial_ends_at.future?
29
+ !!trial_ends_at && trial_ends_at.future?
30
30
  end
31
31
 
32
32
  # Determine if the Stripe model has a given subscription.
33
33
  def subscribed(name = 'default', plan = nil)
34
34
  subscription = self.subscription(name)
35
35
 
36
- return false unless subscription && subscription.valid?
36
+ return false unless subscription&.valid?
37
37
 
38
- plan ? subscription.has_plan(plan) : true
38
+ plan ? subscription.plan?(plan) : true
39
39
  end
40
40
 
41
41
  # Get a subscription instance by name.
42
42
  def subscription(name = 'default')
43
- self.subscriptions
43
+ subscriptions
44
44
  .sort_by { |subscription| subscription.created_at.to_i }
45
45
  .reverse
46
46
  .find { |subscription| subscription.name == name }
47
47
  end
48
48
 
49
49
  # Determine if the customer's subscription has an incomplete payment.
50
- def has_incomplete_payment(name = 'default')
50
+ def incomplete_payment?(name = 'default')
51
51
  subscription = self.subscription(name)
52
52
 
53
- subscription ? subscription.has_incomplete_payment : false
53
+ subscription ? subscription.incomplete_payment? : false
54
54
  end
55
55
 
56
56
  # Determine if the Stripe model is actively subscribed to one of the given plans.
57
57
  def subscribed_to_plan(plans, name = 'default')
58
58
  subscription = self.subscription(name)
59
59
 
60
- return false unless subscription && subscription.valid?
60
+ return false unless subscription&.valid?
61
61
 
62
62
  plans = [plans] unless plans.instance_of? Array
63
63
 
64
64
  plans.each do |plan|
65
- return true if subscription.has_plan(plan)
65
+ return true if subscription.plan?(plan)
66
66
  end
67
67
 
68
68
  false
@@ -70,7 +70,7 @@ module Reji
70
70
 
71
71
  # Determine if the entity has a valid subscription on the given plan.
72
72
  def on_plan(plan)
73
- self.subscriptions.any? { |subscription| subscription.valid && subscription.has_plan(plan) }
73
+ subscriptions.any? { |subscription| subscription.valid && subscription.plan?(plan) }
74
74
  end
75
75
 
76
76
  # Get the tax percentage to apply to the subscription.
@@ -80,7 +80,7 @@ module Reji
80
80
 
81
81
  # Get the tax rates to apply to the subscription.
82
82
  def tax_rates
83
- {}
83
+ []
84
84
  end
85
85
 
86
86
  # Get the tax rates to apply to individual subscription items.