reji 1.0.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.
- checksums.yaml +7 -0
- data/.editorconfig +14 -0
- data/.gitattributes +4 -0
- data/.gitignore +15 -0
- data/.travis.yml +28 -0
- data/Appraisals +17 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +133 -0
- data/LICENSE +20 -0
- data/README.md +1285 -0
- data/Rakefile +21 -0
- data/app/controllers/reji/payment_controller.rb +31 -0
- data/app/controllers/reji/webhook_controller.rb +170 -0
- data/app/views/payment.html.erb +228 -0
- data/app/views/receipt.html.erb +250 -0
- data/bin/setup +12 -0
- data/config/routes.rb +6 -0
- data/gemfiles/rails_5.0.gemfile +13 -0
- data/gemfiles/rails_5.1.gemfile +13 -0
- data/gemfiles/rails_5.2.gemfile +13 -0
- data/gemfiles/rails_6.0.gemfile +13 -0
- data/lib/generators/reji/install/install_generator.rb +69 -0
- data/lib/generators/reji/install/templates/db/migrate/add_reji_to_users.rb.erb +16 -0
- data/lib/generators/reji/install/templates/db/migrate/create_subscription_items.rb.erb +19 -0
- data/lib/generators/reji/install/templates/db/migrate/create_subscriptions.rb.erb +22 -0
- data/lib/generators/reji/install/templates/reji.rb +36 -0
- data/lib/reji.rb +75 -0
- data/lib/reji/billable.rb +13 -0
- data/lib/reji/concerns/interacts_with_payment_behavior.rb +33 -0
- data/lib/reji/concerns/manages_customer.rb +113 -0
- data/lib/reji/concerns/manages_invoices.rb +136 -0
- data/lib/reji/concerns/manages_payment_methods.rb +202 -0
- data/lib/reji/concerns/manages_subscriptions.rb +91 -0
- data/lib/reji/concerns/performs_charges.rb +36 -0
- data/lib/reji/concerns/prorates.rb +38 -0
- data/lib/reji/configuration.rb +59 -0
- data/lib/reji/engine.rb +4 -0
- data/lib/reji/errors.rb +66 -0
- data/lib/reji/invoice.rb +243 -0
- data/lib/reji/invoice_line_item.rb +98 -0
- data/lib/reji/payment.rb +61 -0
- data/lib/reji/payment_method.rb +32 -0
- data/lib/reji/subscription.rb +567 -0
- data/lib/reji/subscription_builder.rb +206 -0
- data/lib/reji/subscription_item.rb +97 -0
- data/lib/reji/tax.rb +48 -0
- data/lib/reji/version.rb +5 -0
- data/reji.gemspec +32 -0
- data/spec/dummy/app/models/user.rb +21 -0
- data/spec/dummy/application.rb +53 -0
- data/spec/dummy/config/database.yml +11 -0
- data/spec/dummy/db/schema.rb +40 -0
- data/spec/feature/charges_spec.rb +67 -0
- data/spec/feature/customer_spec.rb +23 -0
- data/spec/feature/invoices_spec.rb +73 -0
- data/spec/feature/multiplan_subscriptions_spec.rb +319 -0
- data/spec/feature/payment_methods_spec.rb +149 -0
- data/spec/feature/pending_updates_spec.rb +77 -0
- data/spec/feature/subscriptions_spec.rb +650 -0
- data/spec/feature/webhooks_spec.rb +162 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/feature_helpers.rb +39 -0
- data/spec/unit/customer_spec.rb +54 -0
- data/spec/unit/invoice_line_item_spec.rb +72 -0
- data/spec/unit/invoice_spec.rb +192 -0
- data/spec/unit/payment_spec.rb +33 -0
- data/spec/unit/subscription_spec.rb +103 -0
- metadata +237 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module ManagesSubscriptions
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
has_many :subscriptions, -> { order(created_at: :desc) }, class_name: 'Reji::Subscription'
|
9
|
+
end
|
10
|
+
|
11
|
+
# Begin creating a new subscription.
|
12
|
+
def new_subscription(name, plans)
|
13
|
+
SubscriptionBuilder.new(self, name, plans)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Determine if the Stripe model is on trial.
|
17
|
+
def on_trial(name = 'default', plan = nil)
|
18
|
+
return true if name == 'default' && plan.nil? && self.on_generic_trial
|
19
|
+
|
20
|
+
subscription = self.subscription(name)
|
21
|
+
|
22
|
+
return false unless subscription && subscription.on_trial
|
23
|
+
|
24
|
+
plan ? subscription.has_plan(plan) : true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Determine if the Stripe model is on a "generic" trial at the model level.
|
28
|
+
def on_generic_trial
|
29
|
+
!! self.trial_ends_at && self.trial_ends_at.future?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Determine if the Stripe model has a given subscription.
|
33
|
+
def subscribed(name = 'default', plan = nil)
|
34
|
+
subscription = self.subscription(name)
|
35
|
+
|
36
|
+
return false unless subscription && subscription.valid?
|
37
|
+
|
38
|
+
plan ? subscription.has_plan(plan) : true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get a subscription instance by name.
|
42
|
+
def subscription(name = 'default')
|
43
|
+
self.subscriptions
|
44
|
+
.sort_by { |subscription| subscription.created_at.to_i }
|
45
|
+
.reverse
|
46
|
+
.find { |subscription| subscription.name == name }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Determine if the customer's subscription has an incomplete payment.
|
50
|
+
def has_incomplete_payment(name = 'default')
|
51
|
+
subscription = self.subscription(name)
|
52
|
+
|
53
|
+
subscription ? subscription.has_incomplete_payment : false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Determine if the Stripe model is actively subscribed to one of the given plans.
|
57
|
+
def subscribed_to_plan(plans, name = 'default')
|
58
|
+
subscription = self.subscription(name)
|
59
|
+
|
60
|
+
return false unless subscription && subscription.valid?
|
61
|
+
|
62
|
+
plans = [plans] unless plans.instance_of? Array
|
63
|
+
|
64
|
+
plans.each do |plan|
|
65
|
+
return true if subscription.has_plan(plan)
|
66
|
+
end
|
67
|
+
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
# Determine if the entity has a valid subscription on the given plan.
|
72
|
+
def on_plan(plan)
|
73
|
+
self.subscriptions.any? { |subscription| subscription.valid && subscription.has_plan(plan) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the tax percentage to apply to the subscription.
|
77
|
+
def tax_percentage
|
78
|
+
0
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get the tax rates to apply to the subscription.
|
82
|
+
def tax_rates
|
83
|
+
{}
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get the tax rates to apply to individual subscription items.
|
87
|
+
def plan_tax_rates
|
88
|
+
{}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module PerformsCharges
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Make a "one off" charge on the customer for the given amount.
|
8
|
+
def charge(amount, payment_method, options = {})
|
9
|
+
options = {
|
10
|
+
:confirmation_method => 'automatic',
|
11
|
+
:confirm => true,
|
12
|
+
:currency => self.preferred_currency,
|
13
|
+
}.merge(options)
|
14
|
+
|
15
|
+
options[:amount] = amount
|
16
|
+
options[:payment_method] = payment_method
|
17
|
+
options[:customer] = self.stripe_id if self.has_stripe_id
|
18
|
+
|
19
|
+
payment = Payment.new(
|
20
|
+
Stripe::PaymentIntent.create(options, self.stripe_options)
|
21
|
+
)
|
22
|
+
|
23
|
+
payment.validate
|
24
|
+
|
25
|
+
payment
|
26
|
+
end
|
27
|
+
|
28
|
+
# Refund a customer for a charge.
|
29
|
+
def refund(payment_intent, options = {})
|
30
|
+
Stripe::Refund.create(
|
31
|
+
{:payment_intent => payment_intent}.merge(options),
|
32
|
+
self.stripe_options
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module Prorates
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Indicate that the plan change should not be prorated.
|
8
|
+
def no_prorate
|
9
|
+
@proration_behavior = 'none'
|
10
|
+
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# Indicate that the plan change should be prorated.
|
15
|
+
def prorate
|
16
|
+
@proration_behavior = 'create_prorations'
|
17
|
+
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Indicate that the plan change should always be invoiced.
|
22
|
+
def always_invoice
|
23
|
+
@proration_behavior = 'always_invoice'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the prorating behavior.
|
27
|
+
def set_proration_behavior(value)
|
28
|
+
@proration_behavior = value
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Determine the prorating behavior when updating the subscription.
|
34
|
+
def prorate_behavior
|
35
|
+
@proration_behavior ||= 'create_prorations'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
class Configuration
|
5
|
+
# Stripe Keys
|
6
|
+
#
|
7
|
+
# The Stripe publishable key and secret key give you access to Stripe's
|
8
|
+
# API. The "publishable" key is typically used when interacting with
|
9
|
+
# Stripe.js while the "secret" key accesses private API endpoints.
|
10
|
+
attr_accessor :key
|
11
|
+
attr_accessor :secret
|
12
|
+
|
13
|
+
# Stripe Webhooks
|
14
|
+
#
|
15
|
+
# Your Stripe webhook secret is used to prevent unauthorized requests to
|
16
|
+
# your Stripe webhook handling controllers. The tolerance setting will
|
17
|
+
# check the drift between the current time and the signed request's.
|
18
|
+
attr_accessor :webhook
|
19
|
+
|
20
|
+
# Reji Model
|
21
|
+
#
|
22
|
+
# This is the model in your application that includes the Billable concern
|
23
|
+
# provided by Reji. It will serve as the primary model you use while
|
24
|
+
# interacting with Reji related methods, subscriptions, and so on.
|
25
|
+
attr_accessor :model
|
26
|
+
attr_accessor :model_id
|
27
|
+
|
28
|
+
# Currency
|
29
|
+
#
|
30
|
+
# This is the default currency that will be used when generating charges
|
31
|
+
# from your application. Of course, you are welcome to use any of the
|
32
|
+
# various world currencies that are currently supported via Stripe.
|
33
|
+
attr_accessor :currency
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@key = ENV['STRIPE_KEY']
|
37
|
+
@secret = ENV['STRIPE_SECRET']
|
38
|
+
@webhook = {
|
39
|
+
:secret => ENV['STRIPE_WEBHOOK_SECRET'],
|
40
|
+
:tolerance => ENV['STRIPE_WEBHOOK_TOLERANCE'] || 300,
|
41
|
+
}
|
42
|
+
@model = ENV['REJI_MODEL'] || 'User'
|
43
|
+
@model_id = ENV['REJI_MODEL_ID'] || 'user_id'
|
44
|
+
@currency = ENV['REJI_CURRENCY'] || 'usd'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.configuration
|
49
|
+
@configuration ||= Configuration.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.configuration=(config)
|
53
|
+
@configuration = config
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.configure
|
57
|
+
yield(configuration)
|
58
|
+
end
|
59
|
+
end
|
data/lib/reji/engine.rb
ADDED
data/lib/reji/errors.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
class IncompletePaymentError < StandardError
|
5
|
+
attr_accessor :payment
|
6
|
+
|
7
|
+
def initialize(payment, message = '')
|
8
|
+
super(message)
|
9
|
+
|
10
|
+
@payment = payment
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class PaymentActionRequiredError < IncompletePaymentError
|
15
|
+
def self.incomplete(payment)
|
16
|
+
self.new(payment, 'The payment attempt failed because additional action is required before it can be completed.')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class PaymentFailureError < IncompletePaymentError
|
21
|
+
def self.invalid_payment_method(payment)
|
22
|
+
self.new(payment, 'The payment attempt failed because of an invalid payment method.')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class CustomerAlreadyCreatedError < StandardError
|
27
|
+
def self.exists(owner)
|
28
|
+
self.new("#{owner.class.name} is already a Stripe customer with ID #{owner.stripe_id}.")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class InvalidCustomerError < StandardError
|
33
|
+
def self.not_yet_created(owner)
|
34
|
+
self.new("#{owner.class.name} is not a Stripe customer yet. See the create_as_stripe_customer method.")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class InvalidPaymentMethodError < StandardError
|
39
|
+
def self.invalid_owner(payment_method, owner)
|
40
|
+
self.new("The payment method `#{payment_method.id}` does not belong to this customer `#{owner.stripe_id}`.")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class InvalidInvoiceError < StandardError
|
45
|
+
def self.invalid_owner(invoice, owner)
|
46
|
+
self.new("The invoice `#{invoice.id}` does not belong to this customer `#{owner.stripe_id}`.")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class SubscriptionUpdateFailureError < StandardError
|
51
|
+
def self.incomplete_subscription(subscription)
|
52
|
+
self.new("The subscription \"#{subscription.stripe_id}\" cannot be updated because its payment is incomplete.")
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.duplicate_plan(subscription, plan)
|
56
|
+
self.new("The plan \"#{plan}\" is already attached to subscription \"#{subscription.stripe_id}\".")
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.cannot_delete_last_plan(subscription)
|
60
|
+
self.new("The plan on subscription \"#{subscription.stripe_id}\" cannot be removed because it is the last one.")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class AccessDeniedHttpError < StandardError
|
65
|
+
end
|
66
|
+
end
|
data/lib/reji/invoice.rb
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wicked_pdf'
|
4
|
+
|
5
|
+
module Reji
|
6
|
+
class Invoice
|
7
|
+
def initialize(owner, invoice)
|
8
|
+
raise Reji::InvalidInvoiceError.invalid_owner(invoice, owner) if owner.stripe_id != invoice.customer
|
9
|
+
|
10
|
+
@owner = owner
|
11
|
+
@invoice = invoice
|
12
|
+
@items = nil
|
13
|
+
@taxes = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get a date for the invoice.
|
17
|
+
def date
|
18
|
+
Time.at(@invoice.created ? @invoice.created : @invoice.date)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get the total amount that was paid (or will be paid).
|
22
|
+
def total
|
23
|
+
Reji.format_amount(self.raw_total)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get the raw total amount that was paid (or will be paid).
|
27
|
+
def raw_total
|
28
|
+
@invoice.total + self.raw_starting_balance
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the total of the invoice (before discounts).
|
32
|
+
def subtotal
|
33
|
+
Reji.format_amount(@invoice[:subtotal])
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determine if the account had a starting balance.
|
37
|
+
def has_starting_balance
|
38
|
+
self.raw_starting_balance < 0
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get the starting balance for the invoice.
|
42
|
+
def starting_balance
|
43
|
+
Reji.format_amount(self.raw_starting_balance)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get the raw starting balance for the invoice.
|
47
|
+
def raw_starting_balance
|
48
|
+
@invoice[:starting_balance] ? @invoice[:starting_balance] : 0
|
49
|
+
end
|
50
|
+
|
51
|
+
# Determine if the invoice has a discount.
|
52
|
+
def has_discount
|
53
|
+
self.raw_discount > 0
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the discount amount.
|
57
|
+
def discount
|
58
|
+
self.format_amount(self.raw_discount)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get the raw discount amount.
|
62
|
+
def raw_discount
|
63
|
+
return 0 unless @invoice.discount
|
64
|
+
|
65
|
+
return (@invoice.subtotal * (self.percent_off / 100)).round.to_i if self.discount_is_percentage
|
66
|
+
|
67
|
+
self.raw_amount_off
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get the coupon code applied to the invoice.
|
71
|
+
def coupon
|
72
|
+
return @invoice[:discount][:coupon][:id] if @invoice[:discount]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Determine if the discount is a percentage.
|
76
|
+
def discount_is_percentage
|
77
|
+
return false unless @invoice[:discount]
|
78
|
+
|
79
|
+
!! @invoice[:discount][:coupon][:percent_off]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get the discount percentage for the invoice.
|
83
|
+
def percent_off
|
84
|
+
self.coupon ? @invoice[:discount][:coupon][:percent_off] : 0
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get the discount amount for the invoice.
|
88
|
+
def amount_off
|
89
|
+
self.format_amount(self.raw_amount_off)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get the raw discount amount for the invoice.
|
93
|
+
def raw_amount_off
|
94
|
+
amount_off = @invoice[:discount][:coupon][:amount_off]
|
95
|
+
|
96
|
+
amount_off ? amount_off : 0
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get the total tax amount.
|
100
|
+
def tax
|
101
|
+
self.format_amount(@invoice.tax)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Determine if the invoice has tax applied.
|
105
|
+
def has_tax
|
106
|
+
line_items = self.invoice_items + self.subscriptions
|
107
|
+
|
108
|
+
line_items.any? { |item| item.has_tax_rates }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Get the taxes applied to the invoice.
|
112
|
+
def taxes
|
113
|
+
return @taxes unless @taxes.nil?
|
114
|
+
|
115
|
+
self.refresh_with_expanded_tax_rates
|
116
|
+
|
117
|
+
@taxes = @invoice.total_tax_amounts
|
118
|
+
.sort_by(&:inclusive)
|
119
|
+
.reverse
|
120
|
+
.map { |tax_amount| Tax.new(tax_amount.amount, @invoice.currency, tax_amount.tax_rate) }
|
121
|
+
|
122
|
+
@taxes
|
123
|
+
end
|
124
|
+
|
125
|
+
# Determine if the customer is not exempted from taxes.
|
126
|
+
def is_not_tax_exempt
|
127
|
+
@invoice[:customer_tax_exempt] == 'none'
|
128
|
+
end
|
129
|
+
|
130
|
+
# Determine if the customer is exempted from taxes.
|
131
|
+
def is_tax_exempt
|
132
|
+
@invoice[:customer_tax_exempt] == 'exempt'
|
133
|
+
end
|
134
|
+
|
135
|
+
# Determine if reverse charge applies to the customer.
|
136
|
+
def reverse_charge_applies
|
137
|
+
@invoice[:customer_tax_exempt] == 'reverse'
|
138
|
+
end
|
139
|
+
|
140
|
+
# Get all of the "invoice item" line items.
|
141
|
+
def invoice_items
|
142
|
+
self.invoice_line_items_by_type('invoiceitem')
|
143
|
+
end
|
144
|
+
|
145
|
+
# Get all of the "subscription" line items.
|
146
|
+
def subscriptions
|
147
|
+
self.invoice_line_items_by_type('subscription')
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get all of the invoice items by a given type.
|
151
|
+
def invoice_line_items_by_type(type)
|
152
|
+
if @items.nil?
|
153
|
+
self.refresh_with_expanded_tax_rates
|
154
|
+
|
155
|
+
@items = @invoice.lines.auto_paging_each
|
156
|
+
end
|
157
|
+
|
158
|
+
@items
|
159
|
+
.select { |item| item.type == type }
|
160
|
+
.map { |item| InvoiceLineItem.new(self, item) }
|
161
|
+
end
|
162
|
+
|
163
|
+
# Get the View instance for the invoice.
|
164
|
+
def view(data)
|
165
|
+
ActionController::Base.new.render_to_string(
|
166
|
+
template: 'receipt',
|
167
|
+
locals: data.merge({
|
168
|
+
invoice: self,
|
169
|
+
owner: self.owner,
|
170
|
+
user: self.owner,
|
171
|
+
})
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Capture the invoice as a PDF and return the raw bytes.
|
176
|
+
def pdf(data)
|
177
|
+
WickedPdf.new.pdf_from_string(self.view(data))
|
178
|
+
end
|
179
|
+
|
180
|
+
# Create an invoice download response.
|
181
|
+
def download(data)
|
182
|
+
filename = "#{data[:product]}_#{self.date.month}_#{self.date.year}"
|
183
|
+
|
184
|
+
self.download_as(filename, data)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Create an invoice download response with a specific filename.
|
188
|
+
def download_as(filename, data)
|
189
|
+
{:data => self.pdf(data), :filename => filename}
|
190
|
+
end
|
191
|
+
|
192
|
+
# Void the Stripe invoice.
|
193
|
+
def void(options = {})
|
194
|
+
@invoice = @invoice.void_invoice(options, @owner.stripe_options)
|
195
|
+
|
196
|
+
self
|
197
|
+
end
|
198
|
+
|
199
|
+
# Get the Stripe model instance.
|
200
|
+
def owner
|
201
|
+
@owner
|
202
|
+
end
|
203
|
+
|
204
|
+
# Get the Stripe invoice instance.
|
205
|
+
def as_stripe_invoice
|
206
|
+
@invoice
|
207
|
+
end
|
208
|
+
|
209
|
+
# Dynamically get values from the Stripe invoice.
|
210
|
+
def method_missing(key)
|
211
|
+
@invoice[key]
|
212
|
+
end
|
213
|
+
|
214
|
+
protected
|
215
|
+
|
216
|
+
# Refresh the invoice with expanded TaxRate objects.
|
217
|
+
def refresh_with_expanded_tax_rates
|
218
|
+
if @invoice.id
|
219
|
+
@invoice = Stripe::Invoice.retrieve({
|
220
|
+
:id => @invoice.id,
|
221
|
+
:expand => [
|
222
|
+
'lines.data.tax_amounts.tax_rate',
|
223
|
+
'total_tax_amounts.tax_rate',
|
224
|
+
],
|
225
|
+
}, @owner.stripe_options)
|
226
|
+
else
|
227
|
+
# If no invoice ID is present then assume this is the customer's upcoming invoice...
|
228
|
+
@invoice = Stripe::Invoice.upcoming({
|
229
|
+
:customer => @owner.stripe_id,
|
230
|
+
:expand => [
|
231
|
+
'lines.data.tax_amounts.tax_rate',
|
232
|
+
'total_tax_amounts.tax_rate',
|
233
|
+
],
|
234
|
+
}, @owner.stripe_options)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Format the given amount into a displayable currency.
|
239
|
+
def format_amount(amount)
|
240
|
+
Reji.format_amount(amount, @invoice.currency)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|