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,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Reji.configure do |config|
|
4
|
+
# Stripe Keys
|
5
|
+
#
|
6
|
+
# The Stripe publishable key and secret key give you access to Stripe's
|
7
|
+
# API. The "publishable" key is typically used when interacting with
|
8
|
+
# Stripe.js while the "secret" key accesses private API endpoints.
|
9
|
+
config.key = ENV['STRIPE_KEY']
|
10
|
+
config.secret = ENV['STRIPE_SECRET']
|
11
|
+
|
12
|
+
# Stripe Webhooks
|
13
|
+
#
|
14
|
+
# Your Stripe webhook secret is used to prevent unauthorized requests to
|
15
|
+
# your Stripe webhook handling controllers. The tolerance setting will
|
16
|
+
# check the drift between the current time and the signed request's.
|
17
|
+
config.webhook = {
|
18
|
+
:secret => ENV['STRIPE_WEBHOOK_SECRET'],
|
19
|
+
:tolerance => ENV['STRIPE_WEBHOOK_TOLERANCE'] || 300,
|
20
|
+
}
|
21
|
+
|
22
|
+
# Reji Model
|
23
|
+
#
|
24
|
+
# This is the model in your application that includes the Billable concern
|
25
|
+
# provided by Reji. It will serve as the primary model you use while
|
26
|
+
# interacting with Reji related methods, subscriptions, and so on.
|
27
|
+
config.model = ENV['REJI_MODEL'] || 'User'
|
28
|
+
config.model_id = ENV['REJI_MODEL_ID'] || 'user_id'
|
29
|
+
|
30
|
+
# Currency
|
31
|
+
#
|
32
|
+
# This is the default currency that will be used when generating charges
|
33
|
+
# from your application. Of course, you are welcome to use any of the
|
34
|
+
# various world currencies that are currently supported via Stripe.
|
35
|
+
config.currency = ENV['REJI_CURRENCY'] || 'usd'
|
36
|
+
end
|
data/lib/reji.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stripe'
|
4
|
+
require 'money'
|
5
|
+
|
6
|
+
require 'reji/engine'
|
7
|
+
require 'reji/configuration'
|
8
|
+
|
9
|
+
require 'reji/concerns/manages_customer'
|
10
|
+
require 'reji/concerns/manages_invoices'
|
11
|
+
require 'reji/concerns/manages_payment_methods'
|
12
|
+
require 'reji/concerns/manages_subscriptions'
|
13
|
+
require 'reji/concerns/performs_charges'
|
14
|
+
require 'reji/concerns/interacts_with_payment_behavior'
|
15
|
+
require 'reji/concerns/prorates'
|
16
|
+
|
17
|
+
require 'reji/billable'
|
18
|
+
require 'reji/errors'
|
19
|
+
require 'reji/invoice'
|
20
|
+
require 'reji/invoice_line_item'
|
21
|
+
require 'reji/payment'
|
22
|
+
require 'reji/payment_method'
|
23
|
+
require 'reji/subscription'
|
24
|
+
require 'reji/subscription_builder'
|
25
|
+
require 'reji/subscription_item'
|
26
|
+
require 'reji/tax'
|
27
|
+
|
28
|
+
module Reji
|
29
|
+
# The Stripe API version.
|
30
|
+
STRIPE_VERSION = '2020-08-27'
|
31
|
+
|
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
|
38
|
+
|
39
|
+
# Get the billable entity instance by Stripe ID.
|
40
|
+
def self.find_billable(stripe_id)
|
41
|
+
return if stripe_id.nil?
|
42
|
+
|
43
|
+
model = @configuration.model
|
44
|
+
model.constantize.where(stripe_id: stripe_id).first
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the default Stripe API options.
|
48
|
+
def self.stripe_options(options = {})
|
49
|
+
{
|
50
|
+
:api_key => Reji.configuration.secret,
|
51
|
+
:stripe_version => Reji::STRIPE_VERSION,
|
52
|
+
}.merge(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Format the given amount into a displayable currency.
|
56
|
+
def self.format_amount(amount, currency = nil)
|
57
|
+
currency = 'usd' if currency.nil?
|
58
|
+
|
59
|
+
Money.rounding_mode = BigDecimal::ROUND_HALF_EVEN
|
60
|
+
Money.locale_backend = :i18n
|
61
|
+
|
62
|
+
money = Money.new(amount, Money::Currency.new(currency.upcase))
|
63
|
+
|
64
|
+
money.format
|
65
|
+
end
|
66
|
+
|
67
|
+
# Configure to maintain past due subscriptions as active.
|
68
|
+
def self.keep_past_due_subscriptions_active
|
69
|
+
@@deactivate_past_due = false
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
Stripe.set_app_info('Rails Reji')
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module Billable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
include Reji::ManagesCustomer
|
8
|
+
include Reji::ManagesInvoices
|
9
|
+
include Reji::ManagesPaymentMethods
|
10
|
+
include Reji::ManagesSubscriptions
|
11
|
+
include Reji::PerformsCharges
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module InteractsWithPaymentBehavior
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Allow subscription changes even if payment fails.
|
8
|
+
def allow_payment_failures
|
9
|
+
@payment_behavior = 'allow_incomplete'
|
10
|
+
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set any subscription change as pending until payment is successful.
|
15
|
+
def pending_if_payment_fails
|
16
|
+
@payment_behavior = 'pending_if_incomplete'
|
17
|
+
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Prevent any subscription change if payment is unsuccessful.
|
22
|
+
def error_if_payment_fails
|
23
|
+
@payment_behavior = 'error_if_incomplete'
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Determine the payment behavior when updating the subscription.
|
29
|
+
def payment_behavior
|
30
|
+
@payment_behavior ||= 'allow_incomplete'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module ManagesCustomer
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Determine if the entity has a Stripe customer ID.
|
8
|
+
def has_stripe_id
|
9
|
+
! self.stripe_id.nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
# Create a Stripe customer for the given model.
|
13
|
+
def create_as_stripe_customer(options = {})
|
14
|
+
raise Reji::CustomerAlreadyCreatedError.exists(self) if self.has_stripe_id
|
15
|
+
|
16
|
+
if ! options.key?('email') && self.stripe_email
|
17
|
+
options[:email] = self.stripe_email
|
18
|
+
end
|
19
|
+
|
20
|
+
# Here we will create the customer instance on Stripe and store the ID of the
|
21
|
+
# user from Stripe. This ID will correspond with the Stripe user instances
|
22
|
+
# and allow us to retrieve users from Stripe later when we need to work.
|
23
|
+
customer = Stripe::Customer.create(
|
24
|
+
options, self.stripe_options
|
25
|
+
)
|
26
|
+
|
27
|
+
self.update({:stripe_id => customer.id})
|
28
|
+
|
29
|
+
customer
|
30
|
+
end
|
31
|
+
|
32
|
+
# Update the underlying Stripe customer information for the model.
|
33
|
+
def update_stripe_customer(options = {})
|
34
|
+
Stripe::Customer.update(
|
35
|
+
self.stripe_id, options, self.stripe_options
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the Stripe customer instance for the current user or create one.
|
40
|
+
def create_or_get_stripe_customer(options = {})
|
41
|
+
return self.as_stripe_customer if self.has_stripe_id
|
42
|
+
|
43
|
+
self.create_as_stripe_customer(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get the Stripe customer for the model.
|
47
|
+
def as_stripe_customer
|
48
|
+
self.assert_customer_exists
|
49
|
+
|
50
|
+
Stripe::Customer.retrieve(self.stripe_id, self.stripe_options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get the email address used to create the customer in Stripe.
|
54
|
+
def stripe_email
|
55
|
+
self.email
|
56
|
+
end
|
57
|
+
|
58
|
+
# Apply a coupon to the billable entity.
|
59
|
+
def apply_coupon(coupon)
|
60
|
+
self.assert_customer_exists
|
61
|
+
|
62
|
+
customer = self.as_stripe_customer
|
63
|
+
|
64
|
+
customer.coupon = coupon
|
65
|
+
|
66
|
+
customer.save
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get the Stripe supported currency used by the entity.
|
70
|
+
def preferred_currency
|
71
|
+
Reji.configuration.currency
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the Stripe billing portal for this customer.
|
75
|
+
def billing_portal_url(return_url = nil)
|
76
|
+
self.assert_customer_exists
|
77
|
+
|
78
|
+
session = Stripe::BillingPortal::Session.create({
|
79
|
+
:customer => self.stripe_id,
|
80
|
+
:return_url => return_url || '/',
|
81
|
+
}, self.stripe_options)
|
82
|
+
|
83
|
+
session.url
|
84
|
+
end
|
85
|
+
|
86
|
+
# Determine if the customer is not exempted from taxes.
|
87
|
+
def is_not_tax_exempt
|
88
|
+
self.as_stripe_customer.tax_exempt == 'none'
|
89
|
+
end
|
90
|
+
|
91
|
+
# Determine if the customer is exempted from taxes.
|
92
|
+
def is_tax_exempt
|
93
|
+
self.as_stripe_customer.tax_exempt == 'exempt'
|
94
|
+
end
|
95
|
+
|
96
|
+
# Determine if reverse charge applies to the customer.
|
97
|
+
def reverse_charge_applies
|
98
|
+
self.as_stripe_customer.tax_exempt == 'reverse'
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get the default Stripe API options for the current Billable model.
|
102
|
+
def stripe_options(options = {})
|
103
|
+
Reji.stripe_options(options)
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
# 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
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module ManagesInvoices
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Add an invoice item to the customer's upcoming invoice.
|
8
|
+
def tab(description, amount, options = {})
|
9
|
+
self.assert_customer_exists
|
10
|
+
|
11
|
+
options = {
|
12
|
+
:customer => self.stripe_id,
|
13
|
+
:amount => amount,
|
14
|
+
:currency => self.preferred_currency,
|
15
|
+
:description => description,
|
16
|
+
}.merge(options)
|
17
|
+
|
18
|
+
Stripe::InvoiceItem.create(options, self.stripe_options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Invoice the customer for the given amount and generate an invoice immediately.
|
22
|
+
def invoice_for(description, amount, tab_options = {}, invoice_options = {})
|
23
|
+
self.tab(description, amount, tab_options)
|
24
|
+
|
25
|
+
self.invoice(invoice_options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Invoice the billable entity outside of the regular billing cycle.
|
29
|
+
def invoice(options = {})
|
30
|
+
self.assert_customer_exists
|
31
|
+
|
32
|
+
parameters = options.merge({:customer => self.stripe_id})
|
33
|
+
|
34
|
+
begin
|
35
|
+
stripe_invoice = Stripe::Invoice.create(parameters, self.stripe_options)
|
36
|
+
|
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
|
42
|
+
|
43
|
+
Invoice.new(self, stripe_invoice)
|
44
|
+
rescue Stripe::InvalidRequestError => e
|
45
|
+
false
|
46
|
+
rescue Stripe::CardError => e
|
47
|
+
payment = Payment.new(
|
48
|
+
Stripe::PaymentIntent.retrieve(
|
49
|
+
{:id => stripe_invoice.payment_intent, :expand => ['invoice.subscription']},
|
50
|
+
self.stripe_options
|
51
|
+
)
|
52
|
+
)
|
53
|
+
|
54
|
+
payment.validate
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the entity's upcoming invoice.
|
59
|
+
def upcoming_invoice
|
60
|
+
return unless self.has_stripe_id
|
61
|
+
|
62
|
+
begin
|
63
|
+
stripe_invoice = Stripe::Invoice.upcoming({:customer => self.stripe_id}, self.stripe_options)
|
64
|
+
|
65
|
+
Invoice.new(self, stripe_invoice)
|
66
|
+
rescue Stripe::InvalidRequestError => e
|
67
|
+
#
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Find an invoice by ID.
|
72
|
+
def find_invoice(id)
|
73
|
+
stripe_invoice = nil
|
74
|
+
|
75
|
+
begin
|
76
|
+
stripe_invoice = Stripe::Invoice.retrieve(id, self.stripe_options)
|
77
|
+
rescue => e
|
78
|
+
#
|
79
|
+
end
|
80
|
+
|
81
|
+
stripe_invoice ? Invoice.new(self, stripe_invoice) : nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Find an invoice or throw a 404 or 403 error.
|
85
|
+
def find_invoice_or_fail(id)
|
86
|
+
begin
|
87
|
+
invoice = self.find_invoice(id)
|
88
|
+
rescue InvalidInvoiceError => e
|
89
|
+
raise Reji::AccessDeniedHttpError.new(e.message)
|
90
|
+
end
|
91
|
+
|
92
|
+
raise ActiveRecord::RecordNotFound if invoice.nil?
|
93
|
+
|
94
|
+
invoice
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create an invoice download response.
|
98
|
+
def download_invoice(id, data, filename = nil)
|
99
|
+
invoice = self.find_invoice_or_fail(id)
|
100
|
+
|
101
|
+
filename ? invoice.download_as(filename, data) : invoice.download(data)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Get a collection of the entity's invoices.
|
105
|
+
def invoices(include_pending = false, parameters = {})
|
106
|
+
return [] unless self.has_stripe_id
|
107
|
+
|
108
|
+
invoices = []
|
109
|
+
|
110
|
+
parameters = {:limit => 24}.merge(parameters)
|
111
|
+
|
112
|
+
stripe_invoices = Stripe::Invoice.list(
|
113
|
+
{:customer => self.stripe_id}.merge(parameters),
|
114
|
+
self.stripe_options
|
115
|
+
)
|
116
|
+
|
117
|
+
# Here we will loop through the Stripe invoices and create our own custom Invoice
|
118
|
+
# instances that have more helper methods and are generally more convenient to
|
119
|
+
# 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
|
126
|
+
end
|
127
|
+
|
128
|
+
invoices
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get an array of the entity's invoices.
|
132
|
+
def invoices_include_pending(parameters = {})
|
133
|
+
self.invoices(true, parameters)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reji
|
4
|
+
module ManagesPaymentMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Create a new SetupIntent instance.
|
8
|
+
def create_setup_intent(options = {})
|
9
|
+
Stripe::SetupIntent.create(options, self.stripe_options)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Determines if the customer currently has a default payment method.
|
13
|
+
def has_default_payment_method
|
14
|
+
! self.card_brand.blank?
|
15
|
+
end
|
16
|
+
|
17
|
+
# Determines if the customer currently has at least one payment method.
|
18
|
+
def has_payment_method
|
19
|
+
! self.payment_methods.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get a collection of the entity's payment methods.
|
23
|
+
def payment_methods(parameters = {})
|
24
|
+
return [] unless self.has_stripe_id
|
25
|
+
|
26
|
+
parameters = {:limit => 24}.merge(parameters)
|
27
|
+
|
28
|
+
# "type" is temporarily required by Stripe...
|
29
|
+
payment_methods = Stripe::PaymentMethod.list(
|
30
|
+
{customer: self.stripe_id, type: 'card'}.merge(parameters),
|
31
|
+
self.stripe_options
|
32
|
+
)
|
33
|
+
|
34
|
+
payment_methods.data.map { |payment_method| PaymentMethod.new(self, payment_method) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a payment method to the customer.
|
38
|
+
def add_payment_method(payment_method)
|
39
|
+
self.assert_customer_exists
|
40
|
+
|
41
|
+
stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
|
42
|
+
|
43
|
+
if stripe_payment_method.customer != self.stripe_id
|
44
|
+
stripe_payment_method = stripe_payment_method.attach(
|
45
|
+
{:customer => self.stripe_id}, self.stripe_options
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
PaymentMethod.new(self, stripe_payment_method)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Remove a payment method from the customer.
|
53
|
+
def remove_payment_method(payment_method)
|
54
|
+
self.assert_customer_exists
|
55
|
+
|
56
|
+
stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
|
57
|
+
|
58
|
+
return if stripe_payment_method.customer != self.stripe_id
|
59
|
+
|
60
|
+
customer = self.as_stripe_customer
|
61
|
+
|
62
|
+
default_payment_method = customer.invoice_settings.default_payment_method
|
63
|
+
|
64
|
+
stripe_payment_method.detach({}, self.stripe_options)
|
65
|
+
|
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
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get the default payment method for the entity.
|
76
|
+
def default_payment_method
|
77
|
+
return unless self.has_stripe_id
|
78
|
+
|
79
|
+
customer = Stripe::Customer.retrieve({
|
80
|
+
:id => self.stripe_id,
|
81
|
+
:expand => [
|
82
|
+
'invoice_settings.default_payment_method',
|
83
|
+
'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
|
93
|
+
|
94
|
+
# If we can't find a payment method, try to return a legacy source...
|
95
|
+
customer.default_source
|
96
|
+
end
|
97
|
+
|
98
|
+
# Update customer's default payment method.
|
99
|
+
def update_default_payment_method(payment_method)
|
100
|
+
self.assert_customer_exists
|
101
|
+
|
102
|
+
customer = self.as_stripe_customer
|
103
|
+
|
104
|
+
stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
|
105
|
+
|
106
|
+
# If the customer already has the payment method as their default, we can bail out
|
107
|
+
# of the call now. We don't need to keep adding the same payment method to this
|
108
|
+
# model's account every single time we go through this specific process call.
|
109
|
+
return if stripe_payment_method.id == customer.invoice_settings.default_payment_method
|
110
|
+
|
111
|
+
payment_method = self.add_payment_method(stripe_payment_method)
|
112
|
+
|
113
|
+
customer.invoice_settings = {:default_payment_method => payment_method.id}
|
114
|
+
|
115
|
+
customer.save
|
116
|
+
|
117
|
+
# Next we will get the default payment method for this user so we can update the
|
118
|
+
# payment method details on the record in the database. This will allow us to
|
119
|
+
# show that information on the front-end when updating the payment methods.
|
120
|
+
self.fill_payment_method_details(payment_method)
|
121
|
+
self.save
|
122
|
+
|
123
|
+
payment_method
|
124
|
+
end
|
125
|
+
|
126
|
+
# Synchronises the customer's default payment method from Stripe back into the database.
|
127
|
+
def update_default_payment_method_from_stripe
|
128
|
+
default_payment_method = self.default_payment_method
|
129
|
+
|
130
|
+
if default_payment_method
|
131
|
+
if default_payment_method.instance_of? PaymentMethod
|
132
|
+
self.fill_payment_method_details(
|
133
|
+
default_payment_method.as_stripe_payment_method
|
134
|
+
).save
|
135
|
+
else
|
136
|
+
self.fill_source_details(default_payment_method).save
|
137
|
+
end
|
138
|
+
else
|
139
|
+
self.update({
|
140
|
+
:card_brand => nil,
|
141
|
+
:card_last_four => nil,
|
142
|
+
})
|
143
|
+
end
|
144
|
+
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
# Deletes the entity's payment methods.
|
149
|
+
def delete_payment_methods
|
150
|
+
self.payment_methods.each { |payment_method| payment_method.delete }
|
151
|
+
|
152
|
+
self.update_default_payment_method_from_stripe
|
153
|
+
end
|
154
|
+
|
155
|
+
# Find a PaymentMethod by ID.
|
156
|
+
def find_payment_method(payment_method)
|
157
|
+
stripe_payment_method = nil
|
158
|
+
|
159
|
+
begin
|
160
|
+
stripe_payment_method = self.resolve_stripe_payment_method(payment_method)
|
161
|
+
rescue => e
|
162
|
+
#
|
163
|
+
end
|
164
|
+
|
165
|
+
stripe_payment_method ? PaymentMethod.new(self, stripe_payment_method) : nil
|
166
|
+
end
|
167
|
+
|
168
|
+
protected
|
169
|
+
|
170
|
+
# Fills the model's properties with the payment method from Stripe.
|
171
|
+
def fill_payment_method_details(payment_method)
|
172
|
+
if payment_method.type == 'card'
|
173
|
+
self.card_brand = payment_method.card.brand
|
174
|
+
self.card_last_four = payment_method.card.last4
|
175
|
+
end
|
176
|
+
|
177
|
+
payment_method
|
178
|
+
end
|
179
|
+
|
180
|
+
# Fills the model's properties with the source from Stripe.
|
181
|
+
def fill_source_details(source)
|
182
|
+
if source.instance_of? Stripe::Card
|
183
|
+
self.card_brand = source.brand
|
184
|
+
self.card_last_four = source.last4
|
185
|
+
elsif source.instance_of? Stripe::BankAccount
|
186
|
+
self.card_brand = 'Bank Account'
|
187
|
+
self.card_last_four = source.last4
|
188
|
+
end
|
189
|
+
|
190
|
+
self
|
191
|
+
end
|
192
|
+
|
193
|
+
# Resolve a PaymentMethod ID to a Stripe PaymentMethod object.
|
194
|
+
def resolve_stripe_payment_method(payment_method)
|
195
|
+
return payment_method if payment_method.instance_of? Stripe::PaymentMethod
|
196
|
+
|
197
|
+
Stripe::PaymentMethod.retrieve(
|
198
|
+
payment_method, self.stripe_options
|
199
|
+
)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|