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