pay 2.7.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pay might be problematic. Click here for more details.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -715
  3. data/app/controllers/pay/webhooks/braintree_controller.rb +10 -3
  4. data/app/controllers/pay/webhooks/paddle_controller.rb +7 -8
  5. data/app/controllers/pay/webhooks/stripe_controller.rb +6 -3
  6. data/app/jobs/pay/{email_sync_job.rb → customer_sync_job.rb} +3 -4
  7. data/app/models/pay/application_record.rb +1 -5
  8. data/app/models/pay/charge.rb +54 -17
  9. data/app/models/pay/customer.rb +87 -0
  10. data/app/models/pay/merchant.rb +19 -0
  11. data/app/models/pay/payment_method.rb +41 -0
  12. data/app/models/pay/subscription.rb +42 -30
  13. data/app/models/pay/webhook.rb +36 -0
  14. data/app/views/layouts/pay/application.html.erb +2 -3
  15. data/app/views/pay/payments/show.html.erb +108 -81
  16. data/app/views/pay/user_mailer/receipt.html.erb +2 -2
  17. data/app/views/pay/user_mailer/refund.html.erb +2 -2
  18. data/config/locales/en.yml +1 -1
  19. data/db/migrate/1_create_pay_tables.rb +72 -0
  20. data/lib/generators/active_record/templates/billable_migration.rb +1 -0
  21. data/lib/pay/attributes.rb +74 -0
  22. data/lib/pay/billable/sync_customer.rb +30 -0
  23. data/lib/pay/braintree/billable.rb +133 -110
  24. data/lib/pay/braintree/payment_method.rb +42 -0
  25. data/lib/pay/braintree/subscription.rb +9 -12
  26. data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -1
  27. data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +4 -4
  28. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
  29. data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -1
  30. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +2 -2
  31. data/lib/pay/braintree/webhooks/subscription_went_active.rb +1 -1
  32. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +1 -1
  33. data/lib/pay/braintree.rb +3 -2
  34. data/lib/pay/engine.rb +6 -1
  35. data/lib/pay/fake_processor/billable.rb +45 -21
  36. data/lib/pay/fake_processor/payment_method.rb +21 -0
  37. data/lib/pay/fake_processor/subscription.rb +11 -8
  38. data/lib/pay/fake_processor.rb +2 -1
  39. data/lib/pay/nano_id.rb +13 -0
  40. data/lib/pay/paddle/billable.rb +18 -48
  41. data/lib/pay/paddle/charge.rb +5 -5
  42. data/lib/pay/paddle/payment_method.rb +60 -0
  43. data/lib/pay/paddle/response.rb +0 -0
  44. data/lib/pay/paddle/subscription.rb +49 -8
  45. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
  46. data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
  47. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  48. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +26 -28
  49. data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
  50. data/lib/pay/paddle.rb +7 -3
  51. data/lib/pay/payment.rb +1 -1
  52. data/lib/pay/receipts.rb +35 -7
  53. data/lib/pay/stripe/billable.rb +75 -76
  54. data/lib/pay/stripe/charge.rb +44 -17
  55. data/lib/pay/stripe/merchant.rb +10 -10
  56. data/lib/pay/stripe/payment_method.rb +61 -0
  57. data/lib/pay/stripe/subscription.rb +55 -22
  58. data/lib/pay/stripe/webhooks/account_updated.rb +2 -3
  59. data/lib/pay/stripe/webhooks/charge_refunded.rb +1 -1
  60. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -2
  61. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +3 -1
  62. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +3 -1
  63. data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
  64. data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
  65. data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
  66. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -8
  67. data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
  68. data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
  69. data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
  70. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
  71. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -1
  72. data/lib/pay/stripe/webhooks/subscription_renewing.rb +12 -2
  73. data/lib/pay/stripe.rb +6 -3
  74. data/lib/pay/version.rb +1 -1
  75. data/lib/pay/webhooks/delegator.rb +4 -0
  76. data/lib/pay/webhooks/process_job.rb +9 -0
  77. data/lib/pay/webhooks.rb +1 -0
  78. data/lib/pay.rb +7 -78
  79. data/lib/tasks/pay.rake +20 -0
  80. metadata +23 -36
  81. data/app/models/pay.rb +0 -5
  82. data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
  83. data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
  84. data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
  85. data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -6
  86. data/db/migrate/20210309004259_add_data_to_pay_billable.rb +0 -10
  87. data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +0 -5
  88. data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +0 -7
  89. data/lib/pay/billable/sync_email.rb +0 -40
  90. data/lib/pay/billable.rb +0 -172
@@ -6,7 +6,7 @@ module Pay
6
6
  end
7
7
 
8
8
  def create
9
- delegate_event(verified_event)
9
+ queue_event(verified_event)
10
10
  head :ok
11
11
  rescue ::Braintree::InvalidSignature
12
12
  head :bad_request
@@ -14,8 +14,15 @@ module Pay
14
14
 
15
15
  private
16
16
 
17
- def delegate_event(event)
18
- Pay::Webhooks.instrument type: "braintree.#{event.kind}", event: event
17
+ def queue_event(event)
18
+ return unless Pay::Webhooks.delegator.listening?("braintree.#{event.kind}")
19
+
20
+ record = Pay::Webhook.create(
21
+ processor: :braintree,
22
+ event_type: event.kind,
23
+ event: {bt_signature: params[:bt_signature], bt_payload: params[:bt_payload]}
24
+ )
25
+ Pay::Webhooks::ProcessJob.perform_later(record)
19
26
  end
20
27
 
21
28
  def verified_event
@@ -6,7 +6,7 @@ module Pay
6
6
  end
7
7
 
8
8
  def create
9
- delegate_event(verified_event)
9
+ queue_event(verified_event)
10
10
  head :ok
11
11
  rescue Pay::Paddle::Error
12
12
  head :bad_request
@@ -14,22 +14,21 @@ module Pay
14
14
 
15
15
  private
16
16
 
17
- def delegate_event(event)
18
- Pay::Webhooks.instrument type: "paddle.#{type}", event: event
19
- end
17
+ def queue_event(event)
18
+ return unless Pay::Webhooks.delegator.listening?("paddle.#{params[:alert_name]}")
20
19
 
21
- def type
22
- params[:alert_name]
20
+ record = Pay::Webhook.create(processor: :paddle, event_type: params[:alert_name], event: event)
21
+ Pay::Webhooks::ProcessJob.perform_later(record)
23
22
  end
24
23
 
25
24
  def verified_event
26
- event = check_params.as_json
25
+ event = verify_params.as_json
27
26
  verifier = Pay::Paddle::Webhooks::SignatureVerifier.new(event)
28
27
  return event if verifier.verify
29
28
  raise Pay::Paddle::Error, "Unable to verify Paddle webhook event"
30
29
  end
31
30
 
32
- def check_params
31
+ def verify_params
33
32
  params.except(:action, :controller).permit!
34
33
  end
35
34
  end
@@ -6,7 +6,7 @@ module Pay
6
6
  end
7
7
 
8
8
  def create
9
- delegate_event(verified_event)
9
+ queue_event(verified_event)
10
10
  head :ok
11
11
  rescue ::Stripe::SignatureVerificationError => e
12
12
  log_error(e)
@@ -15,8 +15,11 @@ module Pay
15
15
 
16
16
  private
17
17
 
18
- def delegate_event(event)
19
- Pay::Webhooks.instrument type: "stripe.#{event.type}", event: event
18
+ def queue_event(event)
19
+ return unless Pay::Webhooks.delegator.listening?("stripe.#{event.type}")
20
+
21
+ record = Pay::Webhook.create(processor: :stripe, event_type: event.type, event: event)
22
+ Pay::Webhooks::ProcessJob.perform_later(record)
20
23
  end
21
24
 
22
25
  def verified_event
@@ -1,10 +1,9 @@
1
1
  module Pay
2
- class EmailSyncJob < ApplicationJob
2
+ class CustomerSyncJob < ApplicationJob
3
3
  queue_as :default
4
4
 
5
- def perform(id, class_name)
6
- billable = class_name.constantize.find(id)
7
- billable.sync_email_with_processor
5
+ def perform(pay_customer_id)
6
+ Pay::Customer.find(pay_customer_id).update_customer!
8
7
  rescue ActiveRecord::RecordNotFound
9
8
  Rails.logger.info "Couldn't find a #{class_name} with ID = #{id}"
10
9
  end
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  class ApplicationRecord < Pay.model_parent_class.constantize
3
3
  self.abstract_class = true
4
-
5
- def self.json_column?(name)
6
- return unless connected? && table_exists?
7
- [:json, :jsonb].include?(attribute_types[name].type)
8
- end
4
+ self.table_name_prefix = "pay_"
9
5
  end
10
6
  end
@@ -1,44 +1,56 @@
1
1
  module Pay
2
2
  class Charge < Pay::ApplicationRecord
3
- self.table_name = Pay.chargeable_table
4
-
5
- # Only serialize for non-json columns
6
- serialize :data unless json_column?("data")
3
+ self.inheritance_column = nil
7
4
 
8
5
  # Associations
9
- belongs_to :owner, polymorphic: true
10
- belongs_to :subscription, optional: true, class_name: "Pay::Subscription", foreign_key: :pay_subscription_id
6
+ belongs_to :customer
7
+ belongs_to :subscription, optional: true
11
8
 
12
9
  # Scopes
13
10
  scope :sorted, -> { order(created_at: :desc) }
11
+ scope :with_active_customer, -> { joins(:customer).merge(Customer.active) }
12
+ scope :with_deleted_customer, -> { joins(:customer).merge(Customer.deleted) }
14
13
  default_scope -> { sorted }
15
14
 
16
15
  # Validations
17
16
  validates :amount, presence: true
18
- validates :processor, presence: true
19
- validates :processor_id, presence: true
20
- validates :card_type, presence: true
17
+ validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
21
18
 
19
+ # Store the payment method kind (card, paypal, etc)
22
20
  store_accessor :data, :paddle_receipt_url
23
21
  store_accessor :data, :stripe_account
24
22
 
23
+ # Payment method attributes
24
+ store_accessor :data, :payment_method_type # card, paypal, sepa, etc
25
+ store_accessor :data, :brand # Visa, Mastercard, Discover, PayPal
26
+ store_accessor :data, :last4
27
+ store_accessor :data, :exp_month
28
+ store_accessor :data, :exp_year
29
+ store_accessor :data, :email # PayPal email, etc
30
+ store_accessor :data, :username # Venmo
31
+ store_accessor :data, :bank
32
+
25
33
  # Helpers for payment processors
26
34
  %w[braintree stripe paddle fake_processor].each do |processor_name|
27
35
  define_method "#{processor_name}?" do
28
- processor == processor_name
36
+ customer.processor == processor_name
29
37
  end
30
38
 
31
39
  scope processor_name, -> { where(processor: processor_name) }
32
40
  end
33
41
 
34
- def payment_processor
35
- @payment_processor ||= payment_processor_for(processor).new(self)
42
+ def self.find_by_processor_and_id(processor, processor_id)
43
+ joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
36
44
  end
37
45
 
38
- def payment_processor_for(name)
46
+ def self.pay_processor_for(name)
39
47
  "Pay::#{name.to_s.classify}::Charge".constantize
40
48
  end
41
49
 
50
+ def payment_processor
51
+ @payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
52
+ end
53
+
42
54
  def processor_charge
43
55
  payment_processor.charge
44
56
  end
@@ -49,11 +61,36 @@ module Pay
49
61
  end
50
62
 
51
63
  def charged_to
52
- "#{card_type} (**** **** **** #{card_last4})"
53
- end
64
+ case payment_method_type
65
+ when "card"
66
+ "#{brand.titleize} (**** **** **** #{last4})"
67
+ when "paypal"
68
+ "#{brand} (#{email})"
54
69
 
55
- def paypal?
56
- braintree? && card_type == "PayPal"
70
+ # Braintree
71
+ when "venmo"
72
+ "#{brand.titleize} #{username}"
73
+ when "us_bank_account"
74
+ "#{bank} #{last4}"
75
+
76
+ # Stripe
77
+ when "acss_debit"
78
+ "#{bank} #{last4}"
79
+ when "eps", "fpx", "ideal", "p24"
80
+ bank
81
+
82
+ when "au_becs_debit"
83
+ "BECS Debit #{last4}"
84
+
85
+ when "bacs_debit"
86
+ "Bacs Debit #{last4}"
87
+
88
+ when "sepa_debit"
89
+ "SEPA Debit #{last4}"
90
+
91
+ else
92
+ payment_method_type&.titleize
93
+ end
57
94
  end
58
95
  end
59
96
  end
@@ -0,0 +1,87 @@
1
+ module Pay
2
+ class Customer < Pay::ApplicationRecord
3
+ belongs_to :owner, polymorphic: true
4
+ has_many :charges, dependent: :destroy
5
+ has_many :subscriptions, dependent: :destroy
6
+ has_many :payment_methods, dependent: :destroy
7
+ has_one :default_payment_method, -> { where(default: true) }, class_name: "Pay::PaymentMethod"
8
+
9
+ scope :active, -> { where(deleted_at: nil) }
10
+ scope :deleted, -> { where.not(deleted_at: nil) }
11
+
12
+ validates :processor, presence: true
13
+ validates :processor_id, allow_blank: true, uniqueness: {scope: :processor, case_sensitive: true}
14
+
15
+ attribute :plan, :string
16
+ attribute :quantity, :integer
17
+ attribute :payment_method_token, :string
18
+
19
+ # Account(s) for marketplace payments
20
+ store_accessor :data, :stripe_account
21
+ store_accessor :data, :braintree_account
22
+
23
+ delegate :email, to: :owner
24
+ delegate_missing_to :pay_processor
25
+
26
+ def self.processor_for(name)
27
+ "Pay::#{name.to_s.classify}::Billable".constantize
28
+ end
29
+
30
+ def pay_processor
31
+ @pay_processor ||= self.class.processor_for(processor).new(self)
32
+ end
33
+
34
+ def update_payment_method(payment_method_id)
35
+ add_payment_method(payment_method_id, default: true)
36
+ end
37
+
38
+ def subscription(name: Pay.default_product_name)
39
+ subscriptions.loaded? ? subscriptions.reverse.detect { |s| s.name == name } : subscriptions.for_name(name).last
40
+ end
41
+
42
+ def subscribed?(name: Pay.default_product_name, processor_plan: nil)
43
+ subscription = subscription(name: name)
44
+
45
+ return false if subscription.nil?
46
+ return subscription.active? if processor_plan.nil?
47
+
48
+ subscription.active? && subscription.processor_plan == processor_plan
49
+ end
50
+
51
+ def on_trial?(name: Pay.default_product_name, plan: nil)
52
+ sub = subscription(name: name)
53
+ return sub&.on_trial? if plan.nil?
54
+
55
+ sub&.on_trial? && sub.processor_plan == plan
56
+ end
57
+
58
+ def on_trial_or_subscribed?(name: Pay.default_product_name, processor_plan: nil)
59
+ on_trial?(name: name, plan: processor_plan) ||
60
+ subscribed?(name: name, processor_plan: processor_plan)
61
+ end
62
+
63
+ def has_incomplete_payment?
64
+ subscriptions.active.incomplete.any?
65
+ end
66
+
67
+ def customer_name
68
+ [owner.try(:first_name), owner.try(:last_name)].compact.join(" ")
69
+ end
70
+
71
+ def active?
72
+ deleted_at.nil?
73
+ end
74
+
75
+ def deleted?
76
+ deleted_at.present?
77
+ end
78
+
79
+ %w[stripe braintree paddle fake_processor].each do |processor_name|
80
+ scope processor_name, -> { where(processor: processor_name) }
81
+
82
+ define_method "#{processor_name}?" do
83
+ processor == processor_name
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,19 @@
1
+ module Pay
2
+ class Merchant < Pay::ApplicationRecord
3
+ belongs_to :owner, polymorphic: true
4
+
5
+ validates :processor, presence: true
6
+
7
+ store_accessor :data, :onboarding_complete
8
+
9
+ delegate_missing_to :pay_processor
10
+
11
+ def self.processor_for(name)
12
+ "Pay::#{name.to_s.classify}::Merchant".constantize
13
+ end
14
+
15
+ def pay_processor
16
+ @pay_processor ||= self.class.processor_for(processor).new(self)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ module Pay
2
+ class PaymentMethod < Pay::ApplicationRecord
3
+ self.inheritance_column = nil
4
+
5
+ belongs_to :customer
6
+
7
+ store_accessor :data, :brand # Visa, Mastercard, Discover, PayPal
8
+ store_accessor :data, :last4
9
+ store_accessor :data, :exp_month
10
+ store_accessor :data, :exp_year
11
+ store_accessor :data, :email # PayPal email, etc
12
+ store_accessor :data, :username
13
+ store_accessor :data, :bank
14
+
15
+ # Aliases to share PaymentMethodAttributes
16
+ alias_attribute :payment_method_type, :type
17
+
18
+ validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
19
+
20
+ def self.find_by_processor_and_id(processor, processor_id)
21
+ joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
22
+ end
23
+
24
+ def self.pay_processor_for(name)
25
+ "Pay::#{name.to_s.classify}::PaymentMethod".constantize
26
+ end
27
+
28
+ def payment_processor
29
+ @payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
30
+ end
31
+
32
+ def make_default!
33
+ return if default?
34
+
35
+ payment_processor.make_default!
36
+
37
+ customer.payment_methods.update_all(default: false)
38
+ update!(default: true)
39
+ end
40
+ end
41
+ end
@@ -1,24 +1,11 @@
1
1
  module Pay
2
2
  class Subscription < Pay::ApplicationRecord
3
- self.table_name = Pay.subscription_table
4
-
5
3
  STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid paused]
6
4
 
7
- # Only serialize for non-json columns
8
- serialize :data unless json_column?("data")
9
-
10
5
  # Associations
11
- belongs_to :owner, polymorphic: true
6
+ belongs_to :customer
12
7
  has_many :charges, class_name: "Pay::Charge", foreign_key: :pay_subscription_id
13
8
 
14
- # Validations
15
- validates :name, presence: true
16
- validates :processor, presence: true
17
- validates :processor_id, presence: true
18
- validates :processor_plan, presence: true
19
- validates :quantity, presence: true
20
- validates :status, presence: true
21
-
22
9
  # Scopes
23
10
  scope :for_name, ->(name) { where(name: name) }
24
11
  scope :on_trial, -> { where.not(trial_ends_at: nil).where("#{table_name}.trial_ends_at > ?", Time.zone.now) }
@@ -27,8 +14,12 @@ module Pay
27
14
  scope :active, -> { where(ends_at: nil).or(on_grace_period).or(on_trial) }
28
15
  scope :incomplete, -> { where(status: :incomplete) }
29
16
  scope :past_due, -> { where(status: :past_due) }
17
+ scope :with_active_customer, -> { joins(:customer).merge(Customer.active) }
18
+ scope :with_deleted_customer, -> { joins(:customer).merge(Customer.deleted) }
19
+
20
+ # Callbacks
21
+ before_destroy :cancel_if_active
30
22
 
31
- # TODO: Include these with a module
32
23
  store_accessor :data, :paddle_update_url
33
24
  store_accessor :data, :paddle_cancel_url
34
25
  store_accessor :data, :paddle_paused_from
@@ -36,29 +27,40 @@ module Pay
36
27
 
37
28
  attribute :prorate, :boolean, default: true
38
29
 
39
- # Helpers for payment processors
30
+ # Validations
31
+ validates :name, presence: true
32
+ validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
33
+ validates :processor_plan, presence: true
34
+ validates :quantity, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 1}
35
+ validates :status, presence: true
36
+
37
+ delegate :on_grace_period?,
38
+ :paused?,
39
+ :pause,
40
+ :cancel,
41
+ :cancel_now!,
42
+ to: :payment_processor
43
+
44
+ # Helper methods for payment processors
40
45
  %w[braintree stripe paddle fake_processor].each do |processor_name|
41
46
  define_method "#{processor_name}?" do
42
- processor == processor_name
47
+ customer.processor == processor_name
43
48
  end
44
49
 
45
- scope processor_name, -> { where(processor: processor_name) }
50
+ scope processor_name, -> { joins(:customer).where(pay_customers: {processor: processor_name}) }
46
51
  end
47
52
 
48
- def payment_processor
49
- @payment_processor ||= payment_processor_for(processor).new(self)
53
+ def self.find_by_processor_and_id(processor, processor_id)
54
+ joins(:customer).find_by(processor_id: processor_id, pay_customers: {processor: processor})
50
55
  end
51
56
 
52
- def payment_processor_for(name)
57
+ def self.pay_processor_for(name)
53
58
  "Pay::#{name.to_s.classify}::Subscription".constantize
54
59
  end
55
60
 
56
- delegate :on_grace_period?,
57
- :paused?,
58
- :pause,
59
- :cancel,
60
- :cancel_now!,
61
- to: :payment_processor
61
+ def payment_processor
62
+ @payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
63
+ end
62
64
 
63
65
  def no_prorate
64
66
  self.prorate = false
@@ -103,13 +105,14 @@ module Pay
103
105
 
104
106
  def resume
105
107
  payment_processor.resume
106
- update(ends_at: nil, status: "active")
108
+ update(ends_at: nil, status: :active)
107
109
  self
108
110
  end
109
111
 
110
112
  def swap(plan)
113
+ raise ArgumentError, "plan must be a string. Got `#{plan.inspect}` instead." unless plan.is_a?(String)
111
114
  payment_processor.swap(plan)
112
- update(processor_plan: plan, ends_at: nil)
115
+ update(processor_plan: plan, ends_at: nil, status: :active)
113
116
  end
114
117
 
115
118
  def swap_and_invoice(plan)
@@ -122,8 +125,17 @@ module Pay
122
125
  end
123
126
 
124
127
  def latest_payment
125
- return unless stripe?
126
128
  processor_subscription(expand: ["latest_invoice.payment_intent"]).latest_invoice.payment_intent
127
129
  end
130
+
131
+ private
132
+
133
+ def cancel_if_active
134
+ if active?
135
+ cancel_now!
136
+ end
137
+ rescue => e
138
+ Rails.logger.info "[Pay] Unable to automatically cancel subscription `#{processor} #{id}`: #{e.message}"
139
+ end
128
140
  end
129
141
  end