pay 2.5.0 → 2.6.4

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -2
  3. data/app/models/pay/charge.rb +22 -3
  4. data/app/models/pay/subscription.rb +23 -24
  5. data/app/views/pay/stripe/_checkout_button.html.erb +21 -0
  6. data/lib/pay.rb +12 -14
  7. data/lib/pay/billable.rb +44 -33
  8. data/lib/pay/billable/sync_email.rb +1 -1
  9. data/lib/pay/braintree.rb +34 -16
  10. data/lib/pay/braintree/authorization_error.rb +9 -0
  11. data/lib/pay/braintree/billable.rb +33 -30
  12. data/lib/pay/braintree/charge.rb +8 -10
  13. data/lib/pay/braintree/error.rb +9 -0
  14. data/lib/pay/braintree/subscription.rb +34 -15
  15. data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +1 -1
  16. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
  17. data/lib/pay/engine.rb +0 -22
  18. data/lib/pay/errors.rb +0 -44
  19. data/lib/pay/fake_processor.rb +8 -0
  20. data/lib/pay/fake_processor/billable.rb +60 -0
  21. data/lib/pay/fake_processor/charge.rb +21 -0
  22. data/lib/pay/fake_processor/error.rb +6 -0
  23. data/lib/pay/fake_processor/subscription.rb +55 -0
  24. data/lib/pay/paddle.rb +30 -16
  25. data/lib/pay/paddle/billable.rb +26 -22
  26. data/lib/pay/paddle/charge.rb +8 -12
  27. data/lib/pay/paddle/error.rb +9 -0
  28. data/lib/pay/paddle/subscription.rb +33 -18
  29. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +1 -1
  30. data/lib/pay/stripe.rb +62 -14
  31. data/lib/pay/stripe/billable.rb +136 -69
  32. data/lib/pay/stripe/charge.rb +9 -15
  33. data/lib/pay/stripe/error.rb +9 -0
  34. data/lib/pay/stripe/subscription.rb +31 -11
  35. data/lib/pay/stripe/webhooks/charge_succeeded.rb +1 -20
  36. data/lib/pay/stripe/webhooks/customer_updated.rb +1 -1
  37. data/lib/pay/stripe/webhooks/payment_method_updated.rb +1 -1
  38. data/lib/pay/version.rb +1 -1
  39. data/lib/pay/webhooks.rb +13 -0
  40. metadata +16 -56
  41. data/lib/pay/braintree/webhooks.rb +0 -11
  42. data/lib/pay/paddle/webhooks.rb +0 -9
  43. data/lib/pay/stripe/webhooks.rb +0 -38
@@ -0,0 +1,60 @@
1
+ module Pay
2
+ module FakeProcessor
3
+ class Billable
4
+ attr_reader :billable
5
+
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
15
+ end
16
+
17
+ def customer
18
+ billable
19
+ end
20
+
21
+ def charge(amount, options = {})
22
+ billable.charges.create(
23
+ processor: :fake_processor,
24
+ processor_id: rand(100_000_000),
25
+ amount: amount,
26
+ card_type: :fake,
27
+ card_last4: 1234,
28
+ card_exp_month: Date.today.month,
29
+ card_exp_year: Date.today.year
30
+ )
31
+ end
32
+
33
+ def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
34
+ subscription = OpenStruct.new id: rand(1_000_000)
35
+ billable.create_pay_subscription(subscription, :fake_processor, name, plan, status: :active, quantity: options.fetch(:quantity, 1))
36
+ end
37
+
38
+ def update_card(payment_method_id)
39
+ billable.update(
40
+ card_type: :fake,
41
+ card_last4: 1234,
42
+ card_exp_month: Date.today.month,
43
+ card_exp_year: Date.today.year
44
+ )
45
+ end
46
+
47
+ def update_email!
48
+ # pass
49
+ end
50
+
51
+ def processor_subscription(subscription_id, options = {})
52
+ billable.subscriptions.find_by(processor: :fake_processor, processor_id: subscription_id)
53
+ end
54
+
55
+ def trial_end_date(subscription)
56
+ Date.today
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,21 @@
1
+ module Pay
2
+ module FakeProcessor
3
+ class Charge
4
+ attr_reader :pay_charge
5
+
6
+ delegate :processor_id, :owner, to: :pay_charge
7
+
8
+ def initialize(pay_charge)
9
+ @pay_charge = pay_charge
10
+ end
11
+
12
+ def charge
13
+ pay_charge
14
+ end
15
+
16
+ def refund!(amount_to_refund)
17
+ pay_charge.update(amount_refunded: amount_to_refund)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module Pay
2
+ module FakeProcessor
3
+ class Error < Pay::Error
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,55 @@
1
+ module Pay
2
+ module FakeProcessor
3
+ class Subscription
4
+ attr_reader :pay_subscription
5
+
6
+ delegate :canceled?,
7
+ :ends_at,
8
+ :on_trial?,
9
+ :owner,
10
+ :processor_subscription,
11
+ :processor_id,
12
+ :prorate,
13
+ :processor_plan,
14
+ :quantity?,
15
+ :quantity,
16
+ to: :pay_subscription
17
+
18
+ def initialize(pay_subscription)
19
+ @pay_subscription = pay_subscription
20
+ end
21
+
22
+ def cancel
23
+ pay_subscription.update(ends_at: Time.current.end_of_month)
24
+ end
25
+
26
+ def cancel_now!
27
+ pay_subscription.update(ends_at: Time.current, status: :canceled)
28
+ end
29
+
30
+ def on_grace_period?
31
+ canceled? && Time.zone.now < ends_at
32
+ end
33
+
34
+ def paused?
35
+ false
36
+ end
37
+
38
+ def pause
39
+ raise NotImplementedError, "FakeProcessor does not support pausing subscriptions"
40
+ end
41
+
42
+ def resume
43
+ unless on_grace_period?
44
+ raise StandardError, "You can only resume subscriptions within their grace period."
45
+ end
46
+
47
+ pay_subscription.update(ends_at: nil, status: :active)
48
+ end
49
+
50
+ def swap(plan)
51
+ pay_subscription.update(processor_plan: plan)
52
+ end
53
+ end
54
+ end
55
+ 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,77 @@
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 :active?,
7
+ :canceled?,
8
+ :ends_at,
9
+ :name,
10
+ :on_trial?,
11
+ :owner,
12
+ :paddle_paused_from,
13
+ :processor_id,
14
+ :processor_plan,
15
+ :processor_subscription,
16
+ :prorate,
17
+ :prorate?,
18
+ :quantity,
19
+ :quantity?,
20
+ :trial_ends_at,
21
+ to: :pay_subscription
22
+
23
+ def initialize(pay_subscription)
24
+ @pay_subscription = pay_subscription
10
25
  end
11
26
 
12
- def paddle_cancel
27
+ def cancel
13
28
  subscription = processor_subscription
14
29
  PaddlePay::Subscription::User.cancel(processor_id)
15
30
  if on_trial?
16
- update(status: :canceled, ends_at: trial_ends_at)
31
+ pay_subscription.update(status: :canceled, ends_at: trial_ends_at)
17
32
  else
18
- update(status: :canceled, ends_at: Time.zone.parse(subscription.next_payment[:date]))
33
+ pay_subscription.update(status: :canceled, ends_at: Time.zone.parse(subscription.next_payment[:date]))
19
34
  end
20
35
  rescue ::PaddlePay::PaddlePayError => e
21
36
  raise Pay::Paddle::Error, e
22
37
  end
23
38
 
24
- def paddle_cancel_now!
39
+ def cancel_now!
25
40
  PaddlePay::Subscription::User.cancel(processor_id)
26
- update(status: :canceled, ends_at: Time.zone.now)
41
+ pay_subscription.update(status: :canceled, ends_at: Time.zone.now)
27
42
  rescue ::PaddlePay::PaddlePayError => e
28
43
  raise Pay::Paddle::Error, e
29
44
  end
30
45
 
31
- def paddle_on_grace_period?
46
+ def on_grace_period?
32
47
  canceled? && Time.zone.now < ends_at || paused? && Time.zone.now < paddle_paused_from
33
48
  end
34
49
 
35
- def paddle_paused?
50
+ def paused?
36
51
  paddle_paused_from.present?
37
52
  end
38
53
 
39
- def paddle_pause
54
+ def pause
40
55
  attributes = {pause: true}
41
56
  response = PaddlePay::Subscription::User.update(processor_id, attributes)
42
- update(paddle_paused_from: Time.zone.parse(response[:next_payment][:date]))
57
+ pay_subscription.update(paddle_paused_from: Time.zone.parse(response[:next_payment][:date]))
43
58
  rescue ::PaddlePay::PaddlePayError => e
44
59
  raise Pay::Paddle::Error, e
45
60
  end
46
61
 
47
- def paddle_resume
62
+ def resume
48
63
  unless paused?
49
64
  raise StandardError, "You can only resume paused subscriptions."
50
65
  end
51
66
 
52
67
  attributes = {pause: false}
53
68
  PaddlePay::Subscription::User.update(processor_id, attributes)
54
- update(status: :active, paddle_paused_from: nil)
69
+ pay_subscription.update(status: :active, paddle_paused_from: nil)
55
70
  rescue ::PaddlePay::PaddlePayError => e
56
71
  raise Pay::Paddle::Error, e
57
72
  end
58
73
 
59
- def paddle_swap(plan)
74
+ def swap(plan)
60
75
  attributes = {plan_id: plan, prorate: prorate}
61
76
  attributes[:quantity] = quantity if quantity?
62
77
  PaddlePay::Subscription::User.update(processor_id, attributes)