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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8aa4f779f21c561b6e8b48df4908424198b09413c0e2b580d7d6d2610e4d7429
4
- data.tar.gz: c62e136c8ae6ffed79028922aedfe59f14e77ca235ae407aa5acaed3299918d5
3
+ metadata.gz: 2a72ff3544c6fa85675a96d650f152b8f3593211f0c205a5a10cddca5d1f7c35
4
+ data.tar.gz: b7593ef07b57654f4ee566cf2fc538b495d22a9667ec90c6695759a464968dae
5
5
  SHA512:
6
- metadata.gz: 2154c6f3551b6089c470c71280c2ba9c810858954290fce62c67f2ca502822a4504447ebca822ac1b01f763c6c94095202693dc5b0d75e6dde4f89464530180e
7
- data.tar.gz: 4d497228db1c45863ad0157bca179957f875705621b968eb1d4c0318c3e4f7ab35692260bb838a7f6c63aa39e704ac3398de535804d512461db1a8ac568239c8
6
+ metadata.gz: 572f4f2989bce0a3afa7a483585e3e6f7e96f0487eb34bf5cb790a60a3b66bc0eeafa4ed48a011e00232770b565d1c592491eb613465054cf5de34bae58b168c
7
+ data.tar.gz: cd5059977bd91e4c72b8c51a7f8584b23d15e7bc988ca3421400e39232b781481271a57f06777c71c83cfee73bcab03312de35734c3138e3101e2aa125e4db27
data/README.md CHANGED
@@ -8,9 +8,10 @@ Pay is a payments engine for Ruby on Rails 4.2 and higher.
8
8
 
9
9
  **Current Payment Providers**
10
10
 
11
- - Stripe ([supports SCA](https://stripe.com/docs/strong-customer-authentication) using API version `2020-08-27`)
11
+ - Stripe ([SCA Compatible](https://stripe.com/docs/strong-customer-authentication) using API version `2020-08-27`)
12
+ - Paddle (SCA Compatible & supports PayPal)
12
13
  - Braintree (supports PayPal)
13
- - Paddle (supports PayPal)
14
+ - [Fake Processor](docs/fake_processor.md)
14
15
 
15
16
  Want to add a new payment provider? Contributions are welcome and the instructions [are here](https://github.com/jasoncharnes/pay/wiki/New-Payment-Provider).
16
17
 
@@ -48,6 +49,13 @@ And then execute:
48
49
  bundle
49
50
  ```
50
51
 
52
+ Make sure you've configured your ActionMailer default_url_options so Pay can generate links to for features like Stripe Checkout.
53
+
54
+ ```ruby
55
+ # config/application.rb
56
+ config.action_mailer.default_url_options = { host: "example.com" }
57
+ ```
58
+
51
59
  #### Migrations
52
60
 
53
61
  To add the migrations to your application, run the following migration:
@@ -1,5 +1,5 @@
1
1
  module Pay
2
- class Charge < ApplicationRecord
2
+ class Charge < Pay::ApplicationRecord
3
3
  self.table_name = Pay.chargeable_table
4
4
 
5
5
  # Only serialize for non-json columns
@@ -18,13 +18,32 @@ module Pay
18
18
  validates :processor_id, presence: true
19
19
  validates :card_type, presence: true
20
20
 
21
+ store_accessor :data, :paddle_receipt_url
22
+
23
+ # Helpers for payment processors
24
+ %w[braintree stripe paddle].each do |processor_name|
25
+ define_method "#{processor_name}?" do
26
+ processor == processor_name
27
+ end
28
+
29
+ scope processor_name, -> { where(processor: processor_name) }
30
+ end
31
+
32
+ def payment_processor
33
+ @payment_processor ||= payment_processor_for(processor).new(self)
34
+ end
35
+
36
+ def payment_processor_for(name)
37
+ "Pay::#{name.to_s.classify}::Charge".constantize
38
+ end
39
+
21
40
  def processor_charge
22
- send("#{processor}_charge")
41
+ payment_processor.charge
23
42
  end
24
43
 
25
44
  def refund!(refund_amount = nil)
26
45
  refund_amount ||= amount
27
- send("#{processor}_refund!", refund_amount)
46
+ payment_processor.refund!(refund_amount)
28
47
  end
29
48
 
30
49
  def charged_to
@@ -1,5 +1,5 @@
1
1
  module Pay
2
- class Subscription < ApplicationRecord
2
+ class Subscription < Pay::ApplicationRecord
3
3
  self.table_name = Pay.subscription_table
4
4
 
5
5
  STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid paused]
@@ -27,6 +27,11 @@ module Pay
27
27
  scope :incomplete, -> { where(status: :incomplete) }
28
28
  scope :past_due, -> { where(status: :past_due) }
29
29
 
30
+ # TODO: Include these with a module
31
+ store_accessor :data, :paddle_update_url
32
+ store_accessor :data, :paddle_cancel_url
33
+ store_accessor :data, :paddle_paused_from
34
+
30
35
  attribute :prorate, :boolean, default: true
31
36
 
32
37
  # Helpers for payment processors
@@ -38,6 +43,21 @@ module Pay
38
43
  scope processor_name, -> { where(processor: processor_name) }
39
44
  end
40
45
 
46
+ def payment_processor
47
+ @payment_processor ||= payment_processor_for(processor).new(self)
48
+ end
49
+
50
+ def payment_processor_for(name)
51
+ "Pay::#{name.to_s.classify}::Subscription".constantize
52
+ end
53
+
54
+ delegate :on_grace_period?,
55
+ :paused?,
56
+ :pause,
57
+ :cancel,
58
+ :cancel_now!,
59
+ to: :payment_processor
60
+
41
61
  def no_prorate
42
62
  self.prorate = false
43
63
  end
@@ -58,11 +78,6 @@ module Pay
58
78
  canceled?
59
79
  end
60
80
 
61
- def on_grace_period?
62
- return unless processor?
63
- send("#{processor}_on_grace_period?")
64
- end
65
-
66
81
  def active?
67
82
  ["trialing", "active"].include?(status) && (ends_at.nil? || on_grace_period? || on_trial?)
68
83
  end
@@ -79,30 +94,14 @@ module Pay
79
94
  past_due? || incomplete?
80
95
  end
81
96
 
82
- def paused?
83
- send("#{processor}_paused?")
84
- end
85
-
86
- def pause
87
- send("#{processor}_pause")
88
- end
89
-
90
- def cancel
91
- send("#{processor}_cancel")
92
- end
93
-
94
- def cancel_now!
95
- send("#{processor}_cancel_now!")
96
- end
97
-
98
97
  def resume
99
- send("#{processor}_resume")
98
+ payment_processor.resume
100
99
  update(ends_at: nil, status: "active")
101
100
  self
102
101
  end
103
102
 
104
103
  def swap(plan)
105
- send("#{processor}_swap", plan)
104
+ payment_processor.swap(plan)
106
105
  update(processor_plan: plan, ends_at: nil)
107
106
  end
108
107
 
@@ -0,0 +1,21 @@
1
+ <%= button_tag title,
2
+ id: "checkout-#{session.id}",
3
+ class: local_assigns[:class],
4
+ style: (local_assigns[:class] || local_assigns[:style]) ? local_assigns[:style] : 'background-color:#6772E5;color:#FFF;padding:8px 12px;border:0;border-radius:4px;font-size:1em'
5
+ %>
6
+ <%= tag.div id: "error-for-#{session.id}" %>
7
+
8
+ <script>
9
+ (() => {
10
+ const checkoutButton = document.getElementById("checkout-<%= session.id %>");
11
+ checkoutButton.addEventListener('click', function () {
12
+ Stripe("<%= Pay::Stripe.public_key %>").redirectToCheckout({
13
+ sessionId: '<%= session.id %>'
14
+ }).then(function (result) {
15
+ if (result.error) {
16
+ document.getElementById('error-message').innerText = result.error.message;
17
+ }
18
+ });
19
+ });
20
+ })()
21
+ </script>
data/lib/pay.rb CHANGED
@@ -1,22 +1,20 @@
1
1
  require "pay/version"
2
2
  require "pay/engine"
3
- require "pay/billable"
4
- require "pay/receipts"
5
- require "pay/payment"
6
3
  require "pay/errors"
7
4
 
8
5
  module Pay
9
- module Webhooks
10
- autoload :Delegator, "pay/webhooks/delegator"
11
-
12
- class << self
13
- delegate :configure, :instrument, to: :delegator
14
-
15
- def delegator
16
- @delegator ||= Delegator.new
17
- end
18
- end
19
- end
6
+ autoload :Billable, "pay/billable"
7
+ autoload :Env, "pay/env"
8
+ autoload :Payment, "pay/payment"
9
+ autoload :Receipts, "pay/receipts"
10
+
11
+ # Payment processors
12
+ autoload :Braintree, "pay/braintree"
13
+ autoload :FakeProcessor, "pay/fake_processor"
14
+ autoload :Paddle, "pay/paddle"
15
+ autoload :Stripe, "pay/stripe"
16
+
17
+ autoload :Webhooks, "pay/webhooks"
20
18
 
21
19
  # Define who owns the subscription
22
20
  mattr_accessor :billable_class
data/lib/pay/billable.rb CHANGED
@@ -17,9 +17,6 @@ module Pay
17
17
 
18
18
  included do |base|
19
19
  include Pay::Billable::SyncEmail
20
- include Pay::Stripe::Billable if defined? ::Stripe
21
- include Pay::Braintree::Billable if defined? ::Braintree
22
- include Pay::Paddle::Billable if defined? ::PaddlePay
23
20
 
24
21
  has_many :charges, class_name: Pay.chargeable_class, foreign_key: :owner_id, inverse_of: :owner, as: :owner
25
22
  has_many :subscriptions, class_name: Pay.subscription_class, foreign_key: :owner_id, inverse_of: :owner, as: :owner
@@ -27,19 +24,39 @@ module Pay
27
24
  attribute :plan, :string
28
25
  attribute :quantity, :integer
29
26
  attribute :card_token, :string
27
+ attribute :pay_fake_processor_allowed, :boolean, default: false
28
+
29
+ validate :pay_fake_processor_is_allowed, if: :processor_changed?
30
+ end
31
+
32
+ def payment_processor
33
+ @payment_processor ||= payment_processor_for(processor).new(self)
34
+ end
35
+
36
+ def payment_processor_for(name)
37
+ "Pay::#{name.to_s.classify}::Billable".constantize
30
38
  end
31
39
 
40
+ # Reset the payment processor when it changes
32
41
  def processor=(value)
33
42
  super(value)
34
43
  self.processor_id = nil if processor_changed?
44
+ @payment_processor = nil
35
45
  end
36
46
 
47
+ def processor
48
+ super&.inquiry
49
+ end
50
+
51
+ delegate :charge, to: :payment_processor
52
+ delegate :subscribe, to: :payment_processor
53
+ delegate :update_card, to: :payment_processor
54
+
37
55
  def customer
38
- check_for_processor
39
56
  raise Pay::Error, I18n.t("errors.email_required") if email.nil?
40
57
 
41
- customer = send("#{processor}_customer")
42
- update_card(card_token) if card_token.present?
58
+ customer = payment_processor.customer
59
+ payment_processor.update_card(card_token) if card_token.present?
43
60
  customer
44
61
  end
45
62
 
@@ -47,20 +64,9 @@ module Pay
47
64
  [try(:first_name), try(:last_name)].compact.join(" ")
48
65
  end
49
66
 
50
- def charge(amount_in_cents, options = {})
51
- check_for_processor
52
- send("create_#{processor}_charge", amount_in_cents, options)
53
- end
54
-
55
- def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
56
- check_for_processor
57
- send("create_#{processor}_subscription", name, plan, options)
58
- end
59
-
60
- def update_card(token)
61
- check_for_processor
62
- customer if processor_id.nil?
63
- send("update_#{processor}_card", token)
67
+ def create_setup_intent
68
+ ActiveSupport::Deprecation.warn("This method will be removed in the next release. Use `@billable.payment_processor.create_setup_intent` instead.")
69
+ payment_processor.create_setup_intent
64
70
  end
65
71
 
66
72
  def on_trial?(name: Pay.default_product_name, plan: nil)
@@ -77,8 +83,7 @@ module Pay
77
83
  end
78
84
 
79
85
  def processor_subscription(subscription_id, options = {})
80
- check_for_processor
81
- send("#{processor}_subscription", subscription_id, options)
86
+ payment_processor.processor_subscription(subscription_id, options)
82
87
  end
83
88
 
84
89
  def subscribed?(name: Pay.default_product_name, processor_plan: nil)
@@ -100,26 +105,31 @@ module Pay
100
105
  end
101
106
 
102
107
  def invoice!(options = {})
103
- send("#{processor}_invoice!", options)
108
+ ActiveSupport::Deprecation.warn("This will be removed in the next release. Use `@billable.payment_processor.invoice!` instead.")
109
+ payment_processor.invoice!(options)
104
110
  end
105
111
 
106
112
  def upcoming_invoice
107
- send("#{processor}_upcoming_invoice")
113
+ ActiveSupport::Deprecation.warn("This will be removed in the next release. Use `@billable.payment_processor.upcoming_invoice` instead.")
114
+ payment_processor.upcoming_invoice
108
115
  end
109
116
 
110
117
  def stripe?
118
+ ActiveSupport::Deprecation.warn("This will be removed in the next release. Use `@billable.processor.stripe?` instead.")
111
119
  processor == "stripe"
112
120
  end
113
121
 
114
122
  def braintree?
123
+ ActiveSupport::Deprecation.warn("This will be removed in the next release. Use `@billable.processor.braintree?` instead.")
115
124
  processor == "braintree"
116
125
  end
117
126
 
118
127
  def paypal?
119
- braintree? && card_type == "PayPal"
128
+ card_type == "PayPal"
120
129
  end
121
130
 
122
131
  def paddle?
132
+ ActiveSupport::Deprecation.warn("This will be removed in the next release. Use `@billable.processor.paddle?` instead.")
123
133
  processor == "paddle"
124
134
  end
125
135
 
@@ -127,14 +137,8 @@ module Pay
127
137
  subscription(name: name)&.has_incomplete_payment?
128
138
  end
129
139
 
130
- private
131
-
132
- def check_for_processor
133
- raise StandardError, I18n.t("errors.no_processor", class_name: self.class.name) unless processor
134
- end
135
-
136
140
  # Used for creating a Pay::Subscription in the database
137
- def create_subscription(subscription, processor, name, plan, options = {})
141
+ def create_pay_subscription(subscription, processor, name, plan, options = {})
138
142
  options[:quantity] ||= 1
139
143
 
140
144
  options.merge!(
@@ -142,15 +146,22 @@ module Pay
142
146
  processor: processor,
143
147
  processor_id: subscription.id,
144
148
  processor_plan: plan,
145
- trial_ends_at: send("#{processor}_trial_end_date", subscription),
149
+ trial_ends_at: payment_processor.trial_end_date(subscription),
146
150
  ends_at: nil
147
151
  )
148
152
  subscriptions.create!(options)
149
153
  end
150
154
 
155
+ private
156
+
151
157
  def default_generic_trial?(name, plan)
152
158
  # Generic trials don't have plans or custom names
153
159
  plan.nil? && name == "default" && on_generic_trial?
154
160
  end
161
+
162
+ def pay_fake_processor_is_allowed
163
+ return unless processor == "fake_processor"
164
+ errors.add(:processor, "must be a valid payment processor") unless pay_fake_processor_allowed?
165
+ end
155
166
  end
156
167
  end
@@ -23,7 +23,7 @@ module Pay
23
23
  end
24
24
 
25
25
  def sync_email_with_processor
26
- send("update_#{processor}_email!")
26
+ payment_processor.update_email!
27
27
  end
28
28
 
29
29
  private
data/lib/pay/braintree.rb CHANGED
@@ -1,16 +1,24 @@
1
- require "pay/env"
2
- require "pay/braintree/billable"
3
- require "pay/braintree/charge"
4
- require "pay/braintree/subscription"
5
- require "pay/braintree/webhooks"
6
-
7
1
  module Pay
8
2
  module Braintree
9
- include Env
3
+ autoload :Billable, "pay/braintree/billable"
4
+ autoload :Charge, "pay/braintree/charge"
5
+ autoload :Subscription, "pay/braintree/subscription"
6
+ autoload :Error, "pay/braintree/error"
7
+ autoload :AuthorizationError, "pay/braintree/authorization_error"
8
+
9
+ module Webhooks
10
+ autoload :SubscriptionCanceled, "pay/braintree/webhooks/subscription_canceled"
11
+ autoload :SubscriptionChargedSuccessfully, "pay/braintree/webhooks/subscription_charged_successfully"
12
+ autoload :SubscriptionChargedUnsuccessfully, "pay/braintree/webhooks/subscription_charged_unsuccessfully"
13
+ autoload :SubscriptionExpired, "pay/braintree/webhooks/subscription_expired"
14
+ autoload :SubscriptionTrialEnded, "pay/braintree/webhooks/subscription_trial_ended"
15
+ autoload :SubscriptionWentActive, "pay/braintree/webhooks/subscription_went_active"
16
+ autoload :SubscriptionWentPastDue, "pay/braintree/webhooks/subscription_went_past_due"
17
+ end
10
18
 
11
- extend self
19
+ extend Env
12
20
 
13
- def setup
21
+ def self.setup
14
22
  Pay.braintree_gateway = ::Braintree::Gateway.new(
15
23
  environment: environment.to_sym,
16
24
  merchant_id: merchant_id,
@@ -18,25 +26,35 @@ module Pay
18
26
  private_key: private_key
19
27
  )
20
28
 
21
- Pay.charge_model.include Pay::Braintree::Charge
22
- Pay.subscription_model.include Pay::Braintree::Subscription
23
- Pay.billable_models.each { |model| model.include Pay::Braintree::Billable }
29
+ configure_webhooks
24
30
  end
25
31
 
26
- def public_key
32
+ def self.public_key
27
33
  find_value_by_name(:braintree, :public_key)
28
34
  end
29
35
 
30
- def private_key
36
+ def self.private_key
31
37
  find_value_by_name(:braintree, :private_key)
32
38
  end
33
39
 
34
- def merchant_id
40
+ def self.merchant_id
35
41
  find_value_by_name(:braintree, :merchant_id)
36
42
  end
37
43
 
38
- def environment
44
+ def self.environment
39
45
  find_value_by_name(:braintree, :environment) || "sandbox"
40
46
  end
47
+
48
+ def self.configure_webhooks
49
+ Pay::Webhooks.configure do |events|
50
+ events.subscribe "braintree.subscription_canceled", Pay::Braintree::Webhooks::SubscriptionCanceled.new
51
+ events.subscribe "braintree.subscription_charged_successfully", Pay::Braintree::Webhooks::SubscriptionChargedSuccessfully.new
52
+ events.subscribe "braintree.subscription_charged_unsuccessfully", Pay::Braintree::Webhooks::SubscriptionChargedUnsuccessfully.new
53
+ events.subscribe "braintree.subscription_expired", Pay::Braintree::Webhooks::SubscriptionExpired.new
54
+ events.subscribe "braintree.subscription_trial_ended", Pay::Braintree::Webhooks::SubscriptionTrialEnded.new
55
+ events.subscribe "braintree.subscription_went_active", Pay::Braintree::Webhooks::SubscriptionWentActive.new
56
+ events.subscribe "braintree.subscription_went_past_due", Pay::Braintree::Webhooks::SubscriptionWentPastDue.new
57
+ end
58
+ end
41
59
  end
42
60
  end