reji 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +14 -0
  3. data/.gitattributes +4 -0
  4. data/.gitignore +15 -0
  5. data/.travis.yml +28 -0
  6. data/Appraisals +17 -0
  7. data/Gemfile +8 -0
  8. data/Gemfile.lock +133 -0
  9. data/LICENSE +20 -0
  10. data/README.md +1285 -0
  11. data/Rakefile +21 -0
  12. data/app/controllers/reji/payment_controller.rb +31 -0
  13. data/app/controllers/reji/webhook_controller.rb +170 -0
  14. data/app/views/payment.html.erb +228 -0
  15. data/app/views/receipt.html.erb +250 -0
  16. data/bin/setup +12 -0
  17. data/config/routes.rb +6 -0
  18. data/gemfiles/rails_5.0.gemfile +13 -0
  19. data/gemfiles/rails_5.1.gemfile +13 -0
  20. data/gemfiles/rails_5.2.gemfile +13 -0
  21. data/gemfiles/rails_6.0.gemfile +13 -0
  22. data/lib/generators/reji/install/install_generator.rb +69 -0
  23. data/lib/generators/reji/install/templates/db/migrate/add_reji_to_users.rb.erb +16 -0
  24. data/lib/generators/reji/install/templates/db/migrate/create_subscription_items.rb.erb +19 -0
  25. data/lib/generators/reji/install/templates/db/migrate/create_subscriptions.rb.erb +22 -0
  26. data/lib/generators/reji/install/templates/reji.rb +36 -0
  27. data/lib/reji.rb +75 -0
  28. data/lib/reji/billable.rb +13 -0
  29. data/lib/reji/concerns/interacts_with_payment_behavior.rb +33 -0
  30. data/lib/reji/concerns/manages_customer.rb +113 -0
  31. data/lib/reji/concerns/manages_invoices.rb +136 -0
  32. data/lib/reji/concerns/manages_payment_methods.rb +202 -0
  33. data/lib/reji/concerns/manages_subscriptions.rb +91 -0
  34. data/lib/reji/concerns/performs_charges.rb +36 -0
  35. data/lib/reji/concerns/prorates.rb +38 -0
  36. data/lib/reji/configuration.rb +59 -0
  37. data/lib/reji/engine.rb +4 -0
  38. data/lib/reji/errors.rb +66 -0
  39. data/lib/reji/invoice.rb +243 -0
  40. data/lib/reji/invoice_line_item.rb +98 -0
  41. data/lib/reji/payment.rb +61 -0
  42. data/lib/reji/payment_method.rb +32 -0
  43. data/lib/reji/subscription.rb +567 -0
  44. data/lib/reji/subscription_builder.rb +206 -0
  45. data/lib/reji/subscription_item.rb +97 -0
  46. data/lib/reji/tax.rb +48 -0
  47. data/lib/reji/version.rb +5 -0
  48. data/reji.gemspec +32 -0
  49. data/spec/dummy/app/models/user.rb +21 -0
  50. data/spec/dummy/application.rb +53 -0
  51. data/spec/dummy/config/database.yml +11 -0
  52. data/spec/dummy/db/schema.rb +40 -0
  53. data/spec/feature/charges_spec.rb +67 -0
  54. data/spec/feature/customer_spec.rb +23 -0
  55. data/spec/feature/invoices_spec.rb +73 -0
  56. data/spec/feature/multiplan_subscriptions_spec.rb +319 -0
  57. data/spec/feature/payment_methods_spec.rb +149 -0
  58. data/spec/feature/pending_updates_spec.rb +77 -0
  59. data/spec/feature/subscriptions_spec.rb +650 -0
  60. data/spec/feature/webhooks_spec.rb +162 -0
  61. data/spec/spec_helper.rb +27 -0
  62. data/spec/support/feature_helpers.rb +39 -0
  63. data/spec/unit/customer_spec.rb +54 -0
  64. data/spec/unit/invoice_line_item_spec.rb +72 -0
  65. data/spec/unit/invoice_spec.rb +192 -0
  66. data/spec/unit/payment_spec.rb +33 -0
  67. data/spec/unit/subscription_spec.rb +103 -0
  68. 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
@@ -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