pay 2.4.2 → 2.6.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -4
  3. data/app/controllers/pay/webhooks/braintree_controller.rb +7 -53
  4. data/app/controllers/pay/webhooks/paddle_controller.rb +19 -18
  5. data/app/controllers/pay/webhooks/stripe_controller.rb +47 -0
  6. data/app/models/pay/charge.rb +22 -3
  7. data/app/models/pay/subscription.rb +23 -24
  8. data/app/views/pay/stripe/_checkout_button.html.erb +21 -0
  9. data/config/routes.rb +1 -1
  10. data/lib/pay.rb +13 -12
  11. data/lib/pay/billable.rb +33 -33
  12. data/lib/pay/billable/sync_email.rb +1 -1
  13. data/lib/pay/braintree.rb +34 -15
  14. data/lib/pay/braintree/authorization_error.rb +9 -0
  15. data/lib/pay/braintree/billable.rb +33 -30
  16. data/lib/pay/braintree/charge.rb +8 -10
  17. data/lib/pay/braintree/error.rb +9 -0
  18. data/lib/pay/braintree/subscription.rb +34 -15
  19. data/lib/pay/braintree/webhooks/subscription_canceled.rb +19 -0
  20. data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +24 -0
  21. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +24 -0
  22. data/lib/pay/braintree/webhooks/subscription_expired.rb +19 -0
  23. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +19 -0
  24. data/lib/pay/braintree/webhooks/subscription_went_active.rb +19 -0
  25. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +19 -0
  26. data/lib/pay/engine.rb +0 -23
  27. data/lib/pay/errors.rb +0 -44
  28. data/lib/pay/paddle.rb +30 -16
  29. data/lib/pay/paddle/billable.rb +26 -22
  30. data/lib/pay/paddle/charge.rb +8 -12
  31. data/lib/pay/paddle/error.rb +9 -0
  32. data/lib/pay/paddle/subscription.rb +29 -18
  33. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +3 -3
  34. data/lib/pay/paddle/webhooks/subscription_created.rb +15 -15
  35. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  36. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +11 -11
  37. data/lib/pay/paddle/webhooks/subscription_updated.rb +11 -11
  38. data/lib/pay/stripe.rb +65 -15
  39. data/lib/pay/stripe/billable.rb +136 -69
  40. data/lib/pay/stripe/charge.rb +9 -15
  41. data/lib/pay/stripe/error.rb +9 -0
  42. data/lib/pay/stripe/subscription.rb +27 -11
  43. data/lib/pay/stripe/webhooks/charge_succeeded.rb +1 -20
  44. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -0
  45. data/lib/pay/version.rb +1 -1
  46. data/lib/pay/webhooks.rb +13 -0
  47. data/lib/pay/webhooks/delegator.rb +61 -0
  48. metadata +20 -69
  49. data/lib/pay/paddle/webhooks.rb +0 -1
  50. data/lib/pay/stripe/webhooks.rb +0 -39
@@ -0,0 +1,24 @@
1
+ # A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
2
+
3
+ module Pay
4
+ module Braintree
5
+ module Webhooks
6
+ class SubscriptionChargedSuccessfully
7
+ def call(event)
8
+ subscription = event.subscription
9
+ return if subscription.nil?
10
+
11
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
12
+ return unless pay_subscription.present?
13
+
14
+ billable = pay_subscription.owner
15
+ charge = Pay::Braintree::Billable.new(billable).save_transaction(subscription.transactions.first)
16
+
17
+ if Pay.send_emails
18
+ Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
2
+
3
+ module Pay
4
+ module Braintree
5
+ module Webhooks
6
+ class SubscriptionChargedUnsuccessfully
7
+ def call(event)
8
+ subscription = event.subscription
9
+ return if subscription.nil?
10
+
11
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
12
+ return unless pay_subscription.present?
13
+
14
+ # billable = pay_subscription.owner
15
+ # charge = Pay::Braintree::Billable.new(billable).save_transaction(subscription.transactions.first)
16
+
17
+ # if Pay.send_emails
18
+ # Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
19
+ # end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ # A subscription reaches the specified number of billing cycles and expires.
2
+
3
+ module Pay
4
+ module Braintree
5
+ module Webhooks
6
+ class SubscriptionExpired
7
+ def call(event)
8
+ subscription = event.subscription
9
+ return if subscription.nil?
10
+
11
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
12
+ return unless pay_subscription.present?
13
+
14
+ pay_subscription.update!(ends_at: Time.current, status: :canceled)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # A subscription's trial period ends.
2
+
3
+ module Pay
4
+ module Braintree
5
+ module Webhooks
6
+ class SubscriptionTrialEnded
7
+ def call(event)
8
+ subscription = event.subscription
9
+ return if subscription.nil?
10
+
11
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
12
+ return unless pay_subscription.present?
13
+
14
+ pay_subscription.update(trial_ends_at: Time.zone.now)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # A subscription's first authorized transaction is created, or a successful transaction moves a subscription from the Past Due status to the Active status. Subscriptions with trial periods will not trigger this notification when they move from the trial period into the first billing cycle.
2
+
3
+ module Pay
4
+ module Braintree
5
+ module Webhooks
6
+ class SubscriptionWentActive
7
+ def call(event)
8
+ subscription = event.subscription
9
+ return if subscription.nil?
10
+
11
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
12
+ return unless pay_subscription.present?
13
+
14
+ pay_subscription.update!(status: :active)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # A subscription has moved from the Active status to the Past Due status. This will only be triggered when the initial transaction in a billing cycle is declined. Once the status moves to past due, it will not be triggered again in that billing cycle.
2
+
3
+ module Pay
4
+ module Braintree
5
+ module Webhooks
6
+ class SubscriptionWentPastDue
7
+ def call(event)
8
+ subscription = event.subscription
9
+ return if subscription.nil?
10
+
11
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
12
+ return unless pay_subscription.present?
13
+
14
+ pay_subscription.update!(status: :past_due)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/pay/engine.rb CHANGED
@@ -1,33 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Lint/HandleExceptions
4
- begin
5
- require "braintree"
6
- rescue LoadError
7
- end
8
-
9
- begin
10
- require "stripe"
11
- require "stripe_event"
12
- rescue LoadError
13
- end
14
-
15
- begin
16
- require "paddle_pay"
17
- rescue LoadError
18
- end
19
- # rubocop:enable Lint/HandleExceptions
20
-
21
3
  module Pay
22
4
  class Engine < ::Rails::Engine
23
5
  engine_name "pay"
24
6
 
25
7
  initializer "pay.processors" do |app|
26
- # Include processor backends
27
- require "pay/stripe" if defined? ::Stripe
28
- require "pay/braintree" if defined? ::Braintree
29
- require "pay/paddle" if defined? ::PaddlePay
30
-
31
8
  if Pay.automount_routes
32
9
  app.routes.append do
33
10
  mount Pay::Engine, at: Pay.routes_path, as: "pay"
data/lib/pay/errors.rb CHANGED
@@ -26,48 +26,4 @@ module Pay
26
26
  I18n.t("errors.invalid_payment")
27
27
  end
28
28
  end
29
-
30
- module Braintree
31
- class Error < Error
32
- def message
33
- result.message
34
- end
35
- end
36
-
37
- class AuthorizationError < Braintree::Error
38
- def message
39
- I18n.t("errors.braintree.authorization")
40
- end
41
- end
42
- end
43
-
44
- module Stripe
45
- class Error < Error
46
- def message
47
- I18n.t("errors.stripe.#{result.code}", default: result.message)
48
- end
49
- end
50
- end
51
-
52
- module Paddle
53
- class Error < Error
54
- def message
55
- I18n.t("errors.paddle.#{result.code}", default: result.message)
56
- end
57
- end
58
- end
59
-
60
- class BraintreeError < Braintree::Error
61
- def message
62
- ActiveSupport::Deprecation.warn("Pay::BraintreeError is deprecated. Instead, use `Pay::Braintree::Error`.")
63
- super
64
- end
65
- end
66
-
67
- class BraintreeAuthorizationError < BraintreeError
68
- def message
69
- ActiveSupport::Deprecation.warn("Pay::BraintreeAuthorizationError is deprecated. Instead, use `Pay::Braintree::AuthorizationError`.")
70
- I18n.t("errors.braintree.authorization")
71
- end
72
- end
73
29
  end
data/lib/pay/paddle.rb CHANGED
@@ -1,38 +1,52 @@
1
- require "pay/env"
2
- require "pay/paddle/billable"
3
- require "pay/paddle/charge"
4
- require "pay/paddle/subscription"
5
- require "pay/paddle/webhooks"
6
-
7
1
  module Pay
8
2
  module Paddle
9
- include Env
3
+ autoload :Billable, "pay/paddle/billable"
4
+ autoload :Charge, "pay/paddle/charge"
5
+ autoload :Subscription, "pay/paddle/subscription"
6
+ autoload :Error, "pay/paddle/error"
7
+
8
+ module Webhooks
9
+ autoload :SignatureVerifier, "pay/paddle/webhooks/signature_verifier"
10
+ autoload :SubscriptionCreated, "pay/paddle/webhooks/subscription_created"
11
+ autoload :SubscriptionCancelled, "pay/paddle/webhooks/subscription_cancelled"
12
+ autoload :SubscriptionPaymentRefunded, "pay/paddle/webhooks/subscription_payment_refunded"
13
+ autoload :SubscriptionPaymentSucceeded, "pay/paddle/webhooks/subscription_payment_succeeded"
14
+ autoload :SubscriptionUpdated, "pay/paddle/webhooks/subscription_updated"
15
+ end
10
16
 
11
- extend self
17
+ extend Env
12
18
 
13
- def setup
19
+ def self.setup
14
20
  ::PaddlePay.config.vendor_id = vendor_id
15
21
  ::PaddlePay.config.vendor_auth_code = vendor_auth_code
16
22
 
17
- Pay.charge_model.include Pay::Paddle::Charge
18
- Pay.subscription_model.include Pay::Paddle::Subscription
19
- Pay.billable_models.each { |model| model.include Pay::Paddle::Billable }
23
+ configure_webhooks
20
24
  end
21
25
 
22
- def vendor_id
26
+ def self.vendor_id
23
27
  find_value_by_name(:paddle, :vendor_id)
24
28
  end
25
29
 
26
- def vendor_auth_code
30
+ def self.vendor_auth_code
27
31
  find_value_by_name(:paddle, :vendor_auth_code)
28
32
  end
29
33
 
30
- def public_key_base64
34
+ def self.public_key_base64
31
35
  find_value_by_name(:paddle, :public_key_base64)
32
36
  end
33
37
 
34
- def passthrough(owner:, **options)
38
+ def self.passthrough(owner:, **options)
35
39
  options.merge(owner_sgid: owner.to_sgid.to_s).to_json
36
40
  end
41
+
42
+ def self.configure_webhooks
43
+ Pay::Webhooks.configure do |events|
44
+ events.subscribe "paddle.subscription_created", Pay::Paddle::Webhooks::SubscriptionCreated.new
45
+ events.subscribe "paddle.subscription_updated", Pay::Paddle::Webhooks::SubscriptionUpdated.new
46
+ events.subscribe "paddle.subscription_cancelled", Pay::Paddle::Webhooks::SubscriptionCancelled.new
47
+ events.subscribe "paddle.subscription_payment_succeeded", Pay::Paddle::Webhooks::SubscriptionPaymentSucceeded.new
48
+ events.subscribe "paddle.subscription_payment_refunded", Pay::Paddle::Webhooks::SubscriptionPaymentRefunded.new
49
+ end
50
+ end
37
51
  end
38
52
  end
@@ -1,27 +1,32 @@
1
1
  module Pay
2
2
  module Paddle
3
- module Billable
4
- extend ActiveSupport::Concern
3
+ class Billable
4
+ attr_reader :billable
5
5
 
6
- included do
7
- scope :paddle, -> { where(processor: :paddle) }
6
+ delegate :processor_id,
7
+ :processor_id?,
8
+ :email,
9
+ :customer_name,
10
+ :card_token,
11
+ to: :billable
12
+
13
+ def initialize(billable)
14
+ @billable = billable
8
15
  end
9
16
 
10
- def paddle_customer
17
+ def customer
11
18
  # pass
12
19
  end
13
20
 
14
- def create_paddle_charge(amount, options = {})
21
+ def charge(amount, options = {})
22
+ subscription = billable.subscription
15
23
  return unless subscription.processor_id
16
24
  raise Pay::Error, "A charge_name is required to create a one-time charge" if options[:charge_name].nil?
17
25
  response = PaddlePay::Subscription::Charge.create(subscription.processor_id, amount.to_f / 100, options[:charge_name], options)
18
- charge = charges.find_or_initialize_by(
19
- processor: :paddle,
20
- processor_id: response[:invoice_id]
21
- )
26
+ charge = billable.charges.find_or_initialize_by(processor: :paddle, processor_id: response[:invoice_id])
22
27
  charge.update(
23
28
  amount: Integer(response[:amount].to_f * 100),
24
- card_type: subscription.processor_subscription.payment_information[:payment_method],
29
+ card_type: processor_subscription(subscription.processor_id).payment_information[:payment_method],
25
30
  paddle_receipt_url: response[:receipt_url],
26
31
  created_at: Time.zone.parse(response[:payment_date])
27
32
  )
@@ -30,46 +35,45 @@ module Pay
30
35
  raise Pay::Paddle::Error, e
31
36
  end
32
37
 
33
- def create_paddle_subscription(name, plan, options = {})
38
+ def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
34
39
  # pass
35
40
  end
36
41
 
37
- def update_paddle_card(token)
42
+ def update_card(token)
38
43
  sync_payment_information_from_paddle
39
44
  end
40
45
 
41
- def update_paddle_email!
46
+ def update_email!
42
47
  # pass
43
48
  end
44
49
 
45
- def paddle_trial_end_date(subscription)
50
+ def trial_end_date(subscription)
46
51
  return unless subscription.state == "trialing"
47
52
  Time.zone.parse(subscription.next_payment[:date]).end_of_day
48
53
  end
49
54
 
50
- def paddle_subscription(subscription_id, options = {})
55
+ def processor_subscription(subscription_id, options = {})
51
56
  hash = PaddlePay::Subscription::User.list({subscription_id: subscription_id}, options).try(:first)
52
57
  OpenStruct.new(hash)
53
58
  rescue ::PaddlePay::PaddlePayError => e
54
59
  raise Pay::Paddle::Error, e
55
60
  end
56
61
 
57
- def paddle_invoice!(options = {})
62
+ def invoice!(options = {})
58
63
  # pass
59
64
  end
60
65
 
61
- def paddle_upcoming_invoice
66
+ def upcoming_invoice
62
67
  # pass
63
68
  end
64
69
 
65
- def sync_payment_information_from_paddle
66
- payment_information = paddle_payment_information(subscription.processor_id)
67
- update!(payment_information) unless payment_information.empty?
70
+ def sync_payment_information
71
+ billable.update!(payment_information(billable.subscription.processor_id))
68
72
  rescue ::PaddlePay::PaddlePayError => e
69
73
  raise Pay::Paddle::Error, e
70
74
  end
71
75
 
72
- def paddle_payment_information(subscription_id)
76
+ def payment_information(subscription_id)
73
77
  subscription_user = PaddlePay::Subscription::User.list({subscription_id: subscription_id}).try(:first)
74
78
  payment_information = subscription_user ? subscription_user[:payment_information] : nil
75
79
  return {} if payment_information.nil?
@@ -1,19 +1,15 @@
1
1
  module Pay
2
2
  module Paddle
3
- module Charge
4
- extend ActiveSupport::Concern
3
+ class Charge
4
+ attr_reader :pay_charge
5
5
 
6
- included do
7
- scope :paddle, -> { where(processor: :paddle) }
6
+ delegate :processor_id, :owner, to: :pay_charge
8
7
 
9
- store_accessor :data, :paddle_receipt_url
8
+ def initialize(pay_charge)
9
+ @pay_charge = pay_charge
10
10
  end
11
11
 
12
- def paddle?
13
- processor == "paddle"
14
- end
15
-
16
- def paddle_charge
12
+ def charge
17
13
  return unless owner.subscription
18
14
  payments = PaddlePay::Subscription::Payment.list({subscription_id: owner.subscription.processor_id})
19
15
  charges = payments.select { |p| p[:id].to_s == processor_id }
@@ -22,12 +18,12 @@ module Pay
22
18
  raise Pay::Paddle::Error, e
23
19
  end
24
20
 
25
- def paddle_refund!(amount_to_refund)
21
+ def refund!(amount_to_refund)
26
22
  return unless owner.subscription
27
23
  payments = PaddlePay::Subscription::Payment.list({subscription_id: owner.subscription.processor_id, is_paid: 1})
28
24
  if payments.count > 0
29
25
  PaddlePay::Subscription::Payment.refund(payments.last[:id], {amount: amount_to_refund})
30
- update(amount_refunded: amount_to_refund)
26
+ pay_charge.update(amount_refunded: amount_to_refund)
31
27
  else
32
28
  raise Error, "Payment not found"
33
29
  end
@@ -0,0 +1,9 @@
1
+ module Pay
2
+ module Paddle
3
+ class Error < Pay::Error
4
+ def message
5
+ I18n.t("errors.paddle.#{result.code}", default: result.message)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,62 +1,73 @@
1
1
  module Pay
2
2
  module Paddle
3
- module Subscription
4
- extend ActiveSupport::Concern
3
+ class Subscription
4
+ attr_reader :pay_subscription
5
5
 
6
- included do
7
- store_accessor :data, :paddle_update_url
8
- store_accessor :data, :paddle_cancel_url
9
- store_accessor :data, :paddle_paused_from
6
+ delegate :canceled?,
7
+ :ends_at,
8
+ :on_trial?,
9
+ :owner,
10
+ :paddle_paused_from,
11
+ :processor_id,
12
+ :processor_plan,
13
+ :processor_subscription,
14
+ :prorate,
15
+ :quantity,
16
+ :quantity?,
17
+ to: :pay_subscription
18
+
19
+ def initialize(pay_subscription)
20
+ @pay_subscription = pay_subscription
10
21
  end
11
22
 
12
- def paddle_cancel
23
+ def cancel
13
24
  subscription = processor_subscription
14
25
  PaddlePay::Subscription::User.cancel(processor_id)
15
26
  if on_trial?
16
- update(status: :canceled, ends_at: trial_ends_at)
27
+ pay_subscription.update(status: :canceled, ends_at: trial_ends_at)
17
28
  else
18
- update(status: :canceled, ends_at: Time.zone.parse(subscription.next_payment[:date]))
29
+ pay_subscription.update(status: :canceled, ends_at: Time.zone.parse(subscription.next_payment[:date]))
19
30
  end
20
31
  rescue ::PaddlePay::PaddlePayError => e
21
32
  raise Pay::Paddle::Error, e
22
33
  end
23
34
 
24
- def paddle_cancel_now!
35
+ def cancel_now!
25
36
  PaddlePay::Subscription::User.cancel(processor_id)
26
- update(status: :canceled, ends_at: Time.zone.now)
37
+ pay_subscription.update(status: :canceled, ends_at: Time.zone.now)
27
38
  rescue ::PaddlePay::PaddlePayError => e
28
39
  raise Pay::Paddle::Error, e
29
40
  end
30
41
 
31
- def paddle_on_grace_period?
42
+ def on_grace_period?
32
43
  canceled? && Time.zone.now < ends_at || paused? && Time.zone.now < paddle_paused_from
33
44
  end
34
45
 
35
- def paddle_paused?
46
+ def paused?
36
47
  paddle_paused_from.present?
37
48
  end
38
49
 
39
- def paddle_pause
50
+ def pause
40
51
  attributes = {pause: true}
41
52
  response = PaddlePay::Subscription::User.update(processor_id, attributes)
42
- update(paddle_paused_from: Time.zone.parse(response[:next_payment][:date]))
53
+ pay_subscription.update(paddle_paused_from: Time.zone.parse(response[:next_payment][:date]))
43
54
  rescue ::PaddlePay::PaddlePayError => e
44
55
  raise Pay::Paddle::Error, e
45
56
  end
46
57
 
47
- def paddle_resume
58
+ def resume
48
59
  unless paused?
49
60
  raise StandardError, "You can only resume paused subscriptions."
50
61
  end
51
62
 
52
63
  attributes = {pause: false}
53
64
  PaddlePay::Subscription::User.update(processor_id, attributes)
54
- update(status: :active, paddle_paused_from: nil)
65
+ pay_subscription.update(status: :active, paddle_paused_from: nil)
55
66
  rescue ::PaddlePay::PaddlePayError => e
56
67
  raise Pay::Paddle::Error, e
57
68
  end
58
69
 
59
- def paddle_swap(plan)
70
+ def swap(plan)
60
71
  attributes = {plan_id: plan, prorate: prorate}
61
72
  attributes[:quantity] = quantity if quantity?
62
73
  PaddlePay::Subscription::User.update(processor_id, attributes)