pay 11.8.0 → 12.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/pay/payments_controller.rb +3 -11
  3. data/app/controllers/pay/webhooks/base_controller.rb +50 -0
  4. data/app/controllers/pay/webhooks/braintree_controller.rb +11 -18
  5. data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +8 -26
  6. data/app/controllers/pay/webhooks/paddle_billing_controller.rb +13 -35
  7. data/app/controllers/pay/webhooks/paddle_classic_controller.rb +5 -20
  8. data/app/controllers/pay/webhooks/stripe_controller.rb +13 -29
  9. data/app/models/pay/braintree/charge.rb +6 -12
  10. data/app/models/pay/braintree/payment_method.rb +7 -5
  11. data/app/models/pay/braintree/subscription.rb +39 -44
  12. data/app/models/pay/charge.rb +1 -1
  13. data/app/models/pay/customer.rb +1 -9
  14. data/app/models/pay/lemon_squeezy/charge.rb +63 -61
  15. data/app/models/pay/lemon_squeezy/customer.rb +1 -1
  16. data/app/models/pay/lemon_squeezy/subscription.rb +43 -46
  17. data/app/models/pay/merchant.rb +1 -1
  18. data/app/models/pay/paddle_billing/charge.rb +43 -53
  19. data/app/models/pay/paddle_billing/customer.rb +1 -1
  20. data/app/models/pay/paddle_billing/payment_method.rb +19 -23
  21. data/app/models/pay/paddle_billing/subscription.rb +56 -67
  22. data/app/models/pay/paddle_classic/customer.rb +1 -1
  23. data/app/models/pay/paddle_classic/subscription.rb +42 -44
  24. data/app/models/pay/payment_method.rb +1 -5
  25. data/app/models/pay/stripe/charge.rb +49 -61
  26. data/app/models/pay/stripe/customer.rb +41 -19
  27. data/app/models/pay/stripe/payment_method.rb +25 -30
  28. data/app/models/pay/stripe/subscription.rb +103 -107
  29. data/app/models/pay/subscription.rb +2 -12
  30. data/app/views/layouts/pay/application.html.erb +0 -2
  31. data/app/views/pay/payments/show.html.erb +19 -8
  32. data/lib/pay/braintree/webhooks/subscription.rb +15 -0
  33. data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -7
  34. data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -7
  35. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +1 -7
  36. data/lib/pay/braintree/webhooks/subscription_went_active.rb +2 -8
  37. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +2 -8
  38. data/lib/pay/braintree.rb +1 -2
  39. data/lib/pay/currency.rb +0 -4
  40. data/lib/pay/engine.rb +4 -0
  41. data/lib/pay/errors.rb +4 -0
  42. data/lib/pay/lemon_squeezy.rb +4 -7
  43. data/lib/pay/paddle_billing.rb +4 -1
  44. data/lib/pay/paddle_classic.rb +4 -1
  45. data/lib/pay/payment.rb +2 -12
  46. data/lib/pay/receipts.rb +0 -1
  47. data/lib/pay/stripe/webhooks/subscription_created.rb +2 -4
  48. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -5
  49. data/lib/pay/stripe.rb +2 -2
  50. data/lib/pay/sync.rb +29 -0
  51. data/lib/pay/version.rb +1 -1
  52. data/lib/pay.rb +7 -5
  53. metadata +5 -3
  54. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d074d1f459f7129fb3f7e308075e87054d667f177453a89a08f3b243a4a029a
4
- data.tar.gz: b6d3340eb343e55a964f925357076b76f751a2e1bae87fa262221682869cb9a7
3
+ metadata.gz: 14afa87f45020e488adeabd8512769dfb6505e4a4416fcd785ddfb44b5f1f1aa
4
+ data.tar.gz: 24ac5cb9a899928283198d52ac59f978ba36f5ecbb42b2188605ad4ac47e3fc3
5
5
  SHA512:
6
- metadata.gz: c5b8ad8fad63c66e64d5c4f03f06dd52b06e981342a1388a8b2ec405dd8438f3ba7cbc1256db54658d8e051e84370fb91806a8b3b916674d532c5ed048d5791f
7
- data.tar.gz: cb8f4b0b6ec9450846a947876ac11da6b44d7bab9c595253179df16f50244d76413d491f2fc4ba25248b6101112ba5ba9ef406e1e95c8b6b418c792ed157442e
6
+ metadata.gz: 1485f2c107bb18da658f958d20d6c849b552c487f9479649296c20a46c67fcc99d3e51ea2c28c6c3edf79585a99a6723da3e613f626f69111c50609884a2a549
7
+ data.tar.gz: 2dffe43858ed52b7f63a1249e0dbcfc88911a9e08cfdbf17702381bf75dd4e956b77ae66cdbbcd7d50832a136a108d33dc1daafe42177219c40d475d2842cd4f
@@ -2,21 +2,13 @@ module Pay
2
2
  class PaymentsController < ApplicationController
3
3
  layout "pay/application"
4
4
 
5
- before_action :set_redirect_to
6
-
7
5
  # Intents on a Stripe Connect account are linked with ?stripe_account=acct_123
6
+ # The back link only follows a same-host URL or a path, so it can't be used for an open redirect
8
7
  def show
9
8
  @payment = Payment.from_id(params[:id], stripe_account: params[:stripe_account].presence)
10
- rescue ::Stripe::StripeError => e
9
+ @redirect_to = url_from(params[:back]) || root_path
10
+ rescue Pay::Error => e
11
11
  redirect_to root_path, alert: e.message
12
12
  end
13
-
14
- private
15
-
16
- # Ensure the back parameter is a valid path
17
- # This safely handles XSS or external redirects
18
- def set_redirect_to
19
- @redirect_to = URI.parse(params[:back].to_s).path.presence || root_path
20
- end
21
13
  end
22
14
  end
@@ -0,0 +1,50 @@
1
+ module Pay
2
+ module Webhooks
3
+ # Verifies a processor's webhook request and queues the event for Pay::Webhooks::ProcessJob.
4
+ #
5
+ # Subclasses implement verified_event, which raises a Pay::Error when the signature doesn't
6
+ # check out, and event_type. Processors whose parsed event can't be serialized override
7
+ # event_payload; Stripe overrides queue? to skip test-mode events.
8
+ class BaseController < ActionController::API
9
+ rescue_from Pay::Error, with: :invalid_signature
10
+
11
+ def create
12
+ event = verified_event
13
+ queue_event(event) if queue?(event)
14
+ head :ok
15
+ end
16
+
17
+ private
18
+
19
+ def queue?(event)
20
+ true
21
+ end
22
+
23
+ def event_payload(event)
24
+ event
25
+ end
26
+
27
+ def queue_event(event)
28
+ type = event_type(event)
29
+ return unless Pay::Webhooks.delegator.listening?("#{processor}.#{type}")
30
+
31
+ record = Pay::Webhook.create!(processor: processor, event_type: type, event: event_payload(event))
32
+ Pay::Webhooks::ProcessJob.perform_later(record)
33
+ end
34
+
35
+ # "stripe", "paddle_billing", and so on, from the controller name
36
+ def processor
37
+ controller_name
38
+ end
39
+
40
+ # The request body as a plain hash, for processors that sign the request rather than hand us an event object
41
+ def verified_params
42
+ params.except(:action, :controller).permit!.as_json
43
+ end
44
+
45
+ def invalid_signature
46
+ head :bad_request
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,28 +1,21 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class BraintreeController < ActionController::API
4
- def create
5
- queue_event(verified_event)
6
- head :ok
7
- rescue ::Braintree::InvalidSignature
8
- head :bad_request
9
- end
10
-
3
+ class BraintreeController < BaseController
11
4
  private
12
5
 
13
- def queue_event(event)
14
- return unless Pay::Webhooks.delegator.listening?("braintree.#{event.kind}")
6
+ def verified_event
7
+ Pay.braintree_gateway.webhook_notification.parse(params[:bt_signature], params[:bt_payload])
8
+ rescue ::Braintree::InvalidSignature => e
9
+ raise Pay::Braintree::Error, e
10
+ end
15
11
 
16
- record = Pay::Webhook.create!(
17
- processor: :braintree,
18
- event_type: event.kind,
19
- event: {bt_signature: params[:bt_signature], bt_payload: params[:bt_payload]}
20
- )
21
- Pay::Webhooks::ProcessJob.perform_later(record)
12
+ def event_type(event)
13
+ event.kind
22
14
  end
23
15
 
24
- def verified_event
25
- Pay.braintree_gateway.webhook_notification.parse(params[:bt_signature], params[:bt_payload])
16
+ # The parsed notification can't be serialized, so store the signed payload and parse it again in the job
17
+ def event_payload(event)
18
+ {bt_signature: params[:bt_signature], bt_payload: params[:bt_payload]}
26
19
  end
27
20
  end
28
21
  end
@@ -1,41 +1,23 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class LemonSqueezyController < ActionController::API
4
- def create
5
- if valid_signature?(request.headers["X-Signature"])
6
- queue_event(verify_params.as_json)
7
- head :ok
8
- else
9
- head :bad_request
10
- end
11
- rescue Pay::LemonSqueezy::Error
12
- head :bad_request
13
- end
14
-
3
+ class LemonSqueezyController < BaseController
15
4
  private
16
5
 
17
- def queue_event(event)
18
- return unless Pay::Webhooks.delegator.listening?("lemon_squeezy.#{params[:meta][:event_name]}")
6
+ def verified_event
7
+ raise Pay::LemonSqueezy::Error, "Unable to verify Lemon Squeezy webhook signature" unless valid_signature?(request.headers["X-Signature"])
8
+ verified_params
9
+ end
19
10
 
20
- record = Pay::Webhook.create!(processor: :lemon_squeezy, event_type: params[:meta][:event_name], event: event)
21
- Pay::Webhooks::ProcessJob.perform_later(record)
11
+ def event_type(event)
12
+ event.dig("meta", "event_name")
22
13
  end
23
14
 
24
- # Pass Lemon Squeezy signature from request.headers["X-Signature"]
25
15
  def valid_signature?(signature)
26
16
  return false if signature.blank?
27
17
 
28
- key = Pay::LemonSqueezy.signing_secret
29
- data = request.raw_post
30
- digest = OpenSSL::Digest.new("sha256")
31
-
32
- hmac = OpenSSL::HMAC.hexdigest(digest, key, data)
18
+ hmac = OpenSSL::HMAC.hexdigest("sha256", Pay::LemonSqueezy.signing_secret.to_s, request.raw_post)
33
19
  ActiveSupport::SecurityUtils.secure_compare(hmac, signature)
34
20
  end
35
-
36
- def verify_params
37
- params.except(:action, :controller).permit!
38
- end
39
21
  end
40
22
  end
41
23
  end
@@ -1,48 +1,26 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class PaddleBillingController < ActionController::API
4
- def create
5
- if valid_signature?(request.headers["Paddle-Signature"])
6
- queue_event(verify_params.as_json)
7
- head :ok
8
- else
9
- head :bad_request
10
- end
11
- rescue Pay::PaddleBilling::Error
12
- head :bad_request
13
- end
14
-
3
+ class PaddleBillingController < BaseController
15
4
  private
16
5
 
17
- def queue_event(event)
18
- return unless Pay::Webhooks.delegator.listening?("paddle_billing.#{params[:event_type]}")
19
-
20
- record = Pay::Webhook.create!(processor: :paddle_billing, event_type: params[:event_type], event: event)
21
- Pay::Webhooks::ProcessJob.perform_later(record)
6
+ def verified_event
7
+ raise Pay::PaddleBilling::Error, "Unable to verify Paddle webhook signature" unless valid_signature?(request.headers["Paddle-Signature"])
8
+ verified_params
22
9
  end
23
10
 
24
- # Pass Paddle signature from request.headers["Paddle-Signature"]
25
- def valid_signature?(paddle_signature)
26
- return false if paddle_signature.blank?
27
-
28
- ts_part, h1_part = paddle_signature.split(";")
29
- _, ts = ts_part.split("=")
30
- _, h1 = h1_part.split("=")
31
-
32
- signed_payload = "#{ts}:#{request.raw_post}"
11
+ def event_type(event)
12
+ event["event_type"]
13
+ end
33
14
 
34
- key = Pay::PaddleBilling.signing_secret
35
- data = signed_payload
36
- digest = OpenSSL::Digest.new("sha256")
15
+ # The header looks like "ts=1671552777;h1=eb4d0dc8..."
16
+ def valid_signature?(signature)
17
+ parts = signature.to_s.split(";").filter_map { |part| part.split("=", 2) if part.include?("=") }.to_h
18
+ ts, h1 = parts.values_at("ts", "h1")
19
+ return false if ts.blank? || h1.blank?
37
20
 
38
- hmac = OpenSSL::HMAC.hexdigest(digest, key, data)
39
- return false if h1.nil? || hmac.bytesize != h1.bytesize
21
+ hmac = OpenSSL::HMAC.hexdigest("sha256", Pay::PaddleBilling.signing_secret.to_s, "#{ts}:#{request.raw_post}")
40
22
  ActiveSupport::SecurityUtils.secure_compare(hmac, h1)
41
23
  end
42
-
43
- def verify_params
44
- params.except(:action, :controller).permit!
45
- end
46
24
  end
47
25
  end
48
26
  end
@@ -1,31 +1,16 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class PaddleClassicController < ActionController::API
4
- def create
5
- queue_event(verified_event)
6
- head :ok
7
- rescue Pay::PaddleClassic::Error
8
- head :bad_request
9
- end
10
-
3
+ class PaddleClassicController < BaseController
11
4
  private
12
5
 
13
- def queue_event(event)
14
- return unless Pay::Webhooks.delegator.listening?("paddle_classic.#{params[:alert_name]}")
15
-
16
- record = Pay::Webhook.create!(processor: :paddle_classic, event_type: params[:alert_name], event: event)
17
- Pay::Webhooks::ProcessJob.perform_later(record)
18
- end
19
-
20
6
  def verified_event
21
- event = verify_params.as_json
22
- verifier = Pay::PaddleClassic::Webhooks::SignatureVerifier.new(event)
23
- return event if verifier.verify
7
+ event = verified_params
8
+ return event if Pay::PaddleClassic::Webhooks::SignatureVerifier.new(event).verify
24
9
  raise Pay::PaddleClassic::Error, "Unable to verify Paddle webhook event"
25
10
  end
26
11
 
27
- def verify_params
28
- params.except(:action, :controller).permit!
12
+ def event_type(event)
13
+ event["alert_name"]
29
14
  end
30
15
  end
31
16
  end
@@ -1,46 +1,30 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class StripeController < ActionController::API
4
- def create
5
- event = verified_event
6
- queue_event(event) if event.livemode || Pay::Stripe.webhook_receive_test_events
7
- head :ok
8
- rescue ::Stripe::SignatureVerificationError => e
9
- log_error(e)
10
- head :bad_request
11
- end
12
-
3
+ class StripeController < BaseController
13
4
  private
14
5
 
15
- def queue_event(event)
16
- return unless Pay::Webhooks.delegator.listening?("stripe.#{event.type}")
17
-
18
- record = Pay::Webhook.create!(processor: :stripe, event_type: event.type, event: event)
19
- Pay::Webhooks::ProcessJob.perform_later(record)
20
- end
21
-
22
6
  def verified_event
23
7
  payload = request.body.read
24
8
  signature = request.headers["Stripe-Signature"]
25
- possible_secrets = secrets(payload, signature)
9
+ secrets = Array.wrap(Pay::Stripe.signing_secret)
10
+ raise Pay::Stripe::Error, "Cannot verify signature without a Stripe signing secret" if secrets.empty?
26
11
 
27
- possible_secrets.each_with_index do |secret, i|
12
+ # Several secrets can be configured while rotating; accept the first that verifies
13
+ secrets.each_with_index do |secret, i|
28
14
  return ::Stripe::Webhook.construct_event(payload, signature, secret.to_s)
29
- rescue ::Stripe::SignatureVerificationError
30
- raise if i == possible_secrets.length - 1
31
- next
15
+ rescue ::Stripe::SignatureVerificationError => e
16
+ next unless i == secrets.length - 1
17
+ logger.error e.message
18
+ raise Pay::Stripe::Error, e
32
19
  end
33
20
  end
34
21
 
35
- def secrets(payload, signature)
36
- secret = Pay::Stripe.signing_secret
37
- return Array.wrap(secret) if secret
38
- raise ::Stripe::SignatureVerificationError.new("Cannot verify signature without a Stripe signing secret", signature, http_body: payload)
22
+ def event_type(event)
23
+ event.type
39
24
  end
40
25
 
41
- def log_error(e)
42
- logger.error e.message
43
- e.backtrace.each { |line| logger.error " #{line}" }
26
+ def queue?(event)
27
+ event.livemode || Pay::Stripe.webhook_receive_test_events
44
28
  end
45
29
  end
46
30
  end
@@ -1,20 +1,14 @@
1
1
  module Pay
2
2
  module Braintree
3
3
  class Charge < Pay::Charge
4
- def self.sync(charge_id, object: nil, try: 0, retries: 1)
5
- object ||= Pay.braintree_gateway.transaction.find(charge_id)
4
+ extend Pay::Sync
6
5
 
7
- pay_customer = Pay::Customer.find_by(processor: :braintree, processor_id: object.customer_details.id)
8
- return unless pay_customer
6
+ def self.sync(charge_id, object: nil)
7
+ sync_with_retries do
8
+ transaction = object || Pay.braintree_gateway.transaction.find(charge_id)
9
+ return unless (pay_customer = find_pay_customer(transaction.customer_details.id))
9
10
 
10
- pay_customer.save_transaction(object)
11
- rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
12
- try += 1
13
- if try <= retries
14
- sleep 0.1
15
- retry
16
- else
17
- raise
11
+ pay_customer.save_transaction(transaction)
18
12
  end
19
13
  end
20
14
 
@@ -1,13 +1,15 @@
1
1
  module Pay
2
2
  module Braintree
3
3
  class PaymentMethod < Pay::PaymentMethod
4
- def self.sync(id, object: nil, try: 0, retries: 1)
5
- object ||= Pay.braintree_gateway.payment_method.find(id)
4
+ extend Pay::Sync
6
5
 
7
- pay_customer = Pay::Braintree::Customer.find_by(processor_id: object.customer_id)
8
- return unless pay_customer
6
+ def self.sync(id, object: nil)
7
+ sync_with_retries do
8
+ payment_method = object || Pay.braintree_gateway.payment_method.find(id)
9
+ return unless (pay_customer = find_pay_customer(payment_method.customer_id))
9
10
 
10
- pay_customer.save_payment_method(object, default: object.default?)
11
+ pay_customer.save_payment_method(payment_method, default: payment_method.default?)
12
+ end
11
13
  end
12
14
 
13
15
  # Sets payment method as default
@@ -1,49 +1,44 @@
1
1
  module Pay
2
2
  module Braintree
3
3
  class Subscription < Pay::Subscription
4
- def self.sync(subscription_id, object: nil, name: nil, try: 0, retries: 1)
5
- object ||= Pay.braintree_gateway.subscription.find(subscription_id)
6
-
7
- # Retrieve Pay::Customer
8
- payment_method = Pay.braintree_gateway.payment_method.find(object.payment_method_token)
9
- pay_customer = Pay::Customer.find_by(processor: :braintree, processor_id: payment_method.customer_id)
10
- return unless pay_customer
11
-
12
- # Sync the PaymentMethod since we've got it
13
- pay_customer.save_payment_method(payment_method, default: payment_method.default?)
14
-
15
- attributes = {
16
- created_at: object.created_at,
17
- current_period_end: object.billing_period_end_date,
18
- current_period_start: object.billing_period_start_date,
19
- payment_method_id: object.payment_method_token,
20
- processor_plan: object.plan_id,
21
- status: object.status.parameterize(separator: "_"),
22
- trial_ends_at: (object.created_at + object.trial_duration.send(object.trial_duration_unit) if object.trial_period)
23
- }
24
-
25
- # Canceled subscriptions should have access through the paid_through_date or updated_at
26
- if object.status == "Canceled"
27
- attributes[:ends_at] = object.updated_at
28
-
29
- # Set grace period for subscriptions that are marked to be canceled
30
- elsif object.status == "Active" && object.number_of_billing_cycles
31
- attributes[:ends_at] = object.paid_through_date.end_of_day
32
- end
4
+ extend Pay::Sync
5
+
6
+ def self.sync(subscription_id, object: nil, name: nil)
7
+ sync_with_retries do
8
+ subscription = object || Pay.braintree_gateway.subscription.find(subscription_id)
9
+
10
+ # The customer is only reachable through the subscription's payment method
11
+ payment_method = Pay.braintree_gateway.payment_method.find(subscription.payment_method_token)
12
+ return unless (pay_customer = find_pay_customer(payment_method.customer_id))
13
+
14
+ # Sync the PaymentMethod since we've got it
15
+ pay_customer.save_payment_method(payment_method, default: payment_method.default?)
16
+
17
+ attributes = {
18
+ created_at: subscription.created_at,
19
+ current_period_end: subscription.billing_period_end_date,
20
+ current_period_start: subscription.billing_period_start_date,
21
+ payment_method_id: subscription.payment_method_token,
22
+ processor_plan: subscription.plan_id,
23
+ status: subscription.status.parameterize(separator: "_"),
24
+ trial_ends_at: (subscription.created_at + subscription.trial_duration.send(subscription.trial_duration_unit) if subscription.trial_period)
25
+ }
33
26
 
34
- if (pay_subscription = find_by(customer: pay_customer, processor_id: object.id))
35
- pay_subscription.with_lock { pay_subscription.update!(attributes) }
36
- else
37
- name ||= Pay.default_product_name
38
- create!(attributes.merge(customer: pay_customer, name: name, processor_id: object.id))
39
- end
40
- rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
41
- try += 1
42
- if try <= retries
43
- sleep 0.1
44
- retry
45
- else
46
- raise
27
+ # Canceled subscriptions should have access through the paid_through_date or updated_at
28
+ if subscription.status == "Canceled"
29
+ attributes[:ends_at] = subscription.updated_at
30
+
31
+ # Set grace period for subscriptions that are marked to be canceled
32
+ elsif subscription.status == "Active" && subscription.number_of_billing_cycles
33
+ attributes[:ends_at] = subscription.paid_through_date.end_of_day
34
+ end
35
+
36
+ if (pay_subscription = find_by(customer: pay_customer, processor_id: subscription.id))
37
+ pay_subscription.with_lock { pay_subscription.update!(attributes) }
38
+ else
39
+ name ||= Pay.default_product_name
40
+ create!(attributes.merge(customer: pay_customer, name: name, processor_id: subscription.id))
41
+ end
47
42
  end
48
43
  end
49
44
 
@@ -75,7 +70,7 @@ module Pay
75
70
  end
76
71
 
77
72
  def change_quantity(quantity, **options)
78
- raise NotImplementedError, "Braintree does not support setting quantity on subscriptions"
73
+ raise Pay::NotSupportedError, "Braintree does not support setting quantity on subscriptions"
79
74
  end
80
75
 
81
76
  def paused?
@@ -83,7 +78,7 @@ module Pay
83
78
  end
84
79
 
85
80
  def pause
86
- raise NotImplementedError, "Braintree does not support pausing subscriptions"
81
+ raise Pay::NotSupportedError, "Braintree does not support pausing subscriptions"
87
82
  end
88
83
 
89
84
  def resumable?
@@ -11,7 +11,7 @@ module Pay
11
11
 
12
12
  # Validations
13
13
  validates :amount, presence: true
14
- validates :processor_id, presence: true, uniqueness: {scope: :customer_id, case_sensitive: true}
14
+ validates :processor_id, presence: true
15
15
 
16
16
  delegate :owner, to: :customer
17
17
 
@@ -8,10 +8,8 @@ module Pay
8
8
 
9
9
  scope :active, -> { where(deleted_at: nil) }
10
10
  scope :deleted, -> { where.not(deleted_at: nil) }
11
- scope :not_fake_processor, -> { where.not(processor: :fake_processor) }
12
11
 
13
12
  validates :processor, presence: true
14
- validates :processor_id, allow_blank: true, uniqueness: {scope: :processor, case_sensitive: true}
15
13
 
16
14
  # Account(s) for marketplace payments
17
15
  store_accessor :data, :braintree_account
@@ -58,7 +56,7 @@ module Pay
58
56
  end
59
57
 
60
58
  def has_incomplete_payment?
61
- subscriptions.active.incomplete.any?
59
+ subscriptions.where(status: %w[incomplete past_due]).exists?
62
60
  end
63
61
 
64
62
  def customer_name
@@ -83,12 +81,6 @@ module Pay
83
81
  # If these match, consider it a generic trial
84
82
  subscription.trial_ends_at == subscription.ends_at
85
83
  end
86
-
87
- # Attempts to pay all past_due subscription invoices to bring them back to active state
88
- # Pass in `statuses: []` if you would like to only include specific subscription statuses
89
- def retry_past_due_subscriptions!(status: [:past_due])
90
- subscriptions.where(status: Array.wrap(status)).each(&:pay_open_invoices)
91
- end
92
84
  end
93
85
  end
94
86