pay 2.5.0 → 2.6.0

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8aa4f779f21c561b6e8b48df4908424198b09413c0e2b580d7d6d2610e4d7429
4
- data.tar.gz: c62e136c8ae6ffed79028922aedfe59f14e77ca235ae407aa5acaed3299918d5
3
+ metadata.gz: f67175eed41645fc10868070414e33e205dc6970be39038715fbde5c18496932
4
+ data.tar.gz: bd725f34c55707aaf30c1e46ecc111a9c00b69f8275d87c7d94f8640b24d84c2
5
5
  SHA512:
6
- metadata.gz: 2154c6f3551b6089c470c71280c2ba9c810858954290fce62c67f2ca502822a4504447ebca822ac1b01f763c6c94095202693dc5b0d75e6dde4f89464530180e
7
- data.tar.gz: 4d497228db1c45863ad0157bca179957f875705621b968eb1d4c0318c3e4f7ab35692260bb838a7f6c63aa39e704ac3398de535804d512461db1a8ac568239c8
6
+ metadata.gz: 47b06c20aef2c34454f40bb734c4d1bdd648e9f9f4380c7b5c8d246357036f46b92511bc36ebf7b3b1ec00234700848ad3b12b1eb76c2e274a420e5bc650608b
7
+ data.tar.gz: 033e3b12a1a1a6f9b7b7bb2e447cb4a1acfb0cf3d35da7d5f00e187f6301d5cc59a9a3bec0d1577f8a6764286d93dbfefe16553939fc8c83213ee1c6668b09b9
@@ -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,19 @@
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"
6
+ autoload :Billable, "pay/billable"
7
+ autoload :Env, "pay/env"
8
+ autoload :Payment, "pay/payment"
9
+ autoload :Receipts, "pay/receipts"
11
10
 
12
- class << self
13
- delegate :configure, :instrument, to: :delegator
11
+ # Payment processors
12
+ autoload :Braintree, "pay/braintree"
13
+ autoload :Paddle, "pay/paddle"
14
+ autoload :Stripe, "pay/stripe"
14
15
 
15
- def delegator
16
- @delegator ||= Delegator.new
17
- end
18
- end
19
- end
16
+ autoload :Webhooks, "pay/webhooks"
20
17
 
21
18
  # Define who owns the subscription
22
19
  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
@@ -29,17 +26,34 @@ module Pay
29
26
  attribute :card_token, :string
30
27
  end
31
28
 
29
+ def payment_processor
30
+ @payment_processor ||= payment_processor_for(processor).new(self)
31
+ end
32
+
33
+ def payment_processor_for(name)
34
+ "Pay::#{name.to_s.classify}::Billable".constantize
35
+ end
36
+
37
+ # Reset the payment processor when it changes
32
38
  def processor=(value)
33
39
  super(value)
34
40
  self.processor_id = nil if processor_changed?
41
+ @payment_processor = nil
35
42
  end
36
43
 
44
+ def processor
45
+ super.inquiry
46
+ end
47
+
48
+ delegate :charge, to: :payment_processor
49
+ delegate :subscribe, to: :payment_processor
50
+ delegate :update_card, to: :payment_processor
51
+
37
52
  def customer
38
- check_for_processor
39
53
  raise Pay::Error, I18n.t("errors.email_required") if email.nil?
40
54
 
41
- customer = send("#{processor}_customer")
42
- update_card(card_token) if card_token.present?
55
+ customer = payment_processor.customer
56
+ payment_processor.update_card(card_token) if card_token.present?
43
57
  customer
44
58
  end
45
59
 
@@ -47,20 +61,9 @@ module Pay
47
61
  [try(:first_name), try(:last_name)].compact.join(" ")
48
62
  end
49
63
 
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)
64
+ def create_setup_intent
65
+ ActiveSupport::Deprecation.warn("This method will be removed in the next release. Use `@billable.payment_processor.create_setup_intent` instead.")
66
+ payment_processor.create_setup_intent
64
67
  end
65
68
 
66
69
  def on_trial?(name: Pay.default_product_name, plan: nil)
@@ -77,8 +80,7 @@ module Pay
77
80
  end
78
81
 
79
82
  def processor_subscription(subscription_id, options = {})
80
- check_for_processor
81
- send("#{processor}_subscription", subscription_id, options)
83
+ payment_processor.processor_subscription(subscription_id, options)
82
84
  end
83
85
 
84
86
  def subscribed?(name: Pay.default_product_name, processor_plan: nil)
@@ -100,11 +102,13 @@ module Pay
100
102
  end
101
103
 
102
104
  def invoice!(options = {})
103
- send("#{processor}_invoice!", options)
105
+ ActiveSupport::Deprecation.warn("This will be removed in the next release. Use `@billable.payment_processor.invoice!` instead.")
106
+ payment_processor.invoice!(options)
104
107
  end
105
108
 
106
109
  def upcoming_invoice
107
- send("#{processor}_upcoming_invoice")
110
+ ActiveSupport::Deprecation.warn("This will be removed in the next release. Use `@billable.payment_processor.upcoming_invoice` instead.")
111
+ payment_processor.upcoming_invoice
108
112
  end
109
113
 
110
114
  def stripe?
@@ -116,7 +120,7 @@ module Pay
116
120
  end
117
121
 
118
122
  def paypal?
119
- braintree? && card_type == "PayPal"
123
+ card_type == "PayPal"
120
124
  end
121
125
 
122
126
  def paddle?
@@ -127,14 +131,8 @@ module Pay
127
131
  subscription(name: name)&.has_incomplete_payment?
128
132
  end
129
133
 
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
134
  # Used for creating a Pay::Subscription in the database
137
- def create_subscription(subscription, processor, name, plan, options = {})
135
+ def create_pay_subscription(subscription, processor, name, plan, options = {})
138
136
  options[:quantity] ||= 1
139
137
 
140
138
  options.merge!(
@@ -142,12 +140,14 @@ module Pay
142
140
  processor: processor,
143
141
  processor_id: subscription.id,
144
142
  processor_plan: plan,
145
- trial_ends_at: send("#{processor}_trial_end_date", subscription),
143
+ trial_ends_at: payment_processor.trial_end_date(subscription),
146
144
  ends_at: nil
147
145
  )
148
146
  subscriptions.create!(options)
149
147
  end
150
148
 
149
+ private
150
+
151
151
  def default_generic_trial?(name, plan)
152
152
  # Generic trials don't have plans or custom names
153
153
  plan.nil? && name == "default" && on_generic_trial?
@@ -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
@@ -0,0 +1,9 @@
1
+ module Pay
2
+ module Braintree
3
+ class AuthorizationError < Braintree::Error
4
+ def message
5
+ I18n.t("errors.braintree.authorization")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,16 +1,23 @@
1
1
  module Pay
2
2
  module Braintree
3
- module Billable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- scope :braintree, -> { where(processor: :braintree) }
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
8
15
  end
9
16
 
10
17
  # Handles Billable#customer
11
18
  #
12
19
  # Returns Braintree::Customer
13
- def braintree_customer
20
+ def customer
14
21
  if processor_id?
15
22
  gateway.customer.find(processor_id)
16
23
  else
@@ -22,16 +29,16 @@ module Pay
22
29
  )
23
30
  raise Pay::Braintree::Error, result unless result.success?
24
31
 
25
- update(processor: "braintree", processor_id: result.customer.id)
32
+ billable.update(processor: "braintree", processor_id: result.customer.id)
26
33
 
27
34
  if card_token.present?
28
- update_braintree_card_on_file result.customer.payment_methods.last
35
+ update_card_on_file result.customer.payment_methods.last
29
36
  end
30
37
 
31
38
  result.customer
32
39
  end
33
40
  rescue ::Braintree::AuthorizationError
34
- raise BraintreeAuthorizationError
41
+ raise Pay::Braintree::AuthorizationError
35
42
  rescue ::Braintree::BraintreeError => e
36
43
  raise Pay::Braintree::Error, e
37
44
  end
@@ -39,7 +46,7 @@ module Pay
39
46
  # Handles Billable#charge
40
47
  #
41
48
  # Returns a Pay::Charge
42
- def create_braintree_charge(amount, options = {})
49
+ def charge(amount, options = {})
43
50
  args = {
44
51
  amount: amount.to_i / 100.0,
45
52
  customer_id: customer.id,
@@ -49,7 +56,7 @@ module Pay
49
56
  result = gateway.transaction.sale(args)
50
57
  raise Pay::Braintree::Error, result unless result.success?
51
58
 
52
- save_braintree_transaction(result.transaction)
59
+ save_transaction(result.transaction)
53
60
  rescue ::Braintree::AuthorizationError
54
61
  raise Pay::Braintree::AuthorizationError
55
62
  rescue ::Braintree::BraintreeError => e
@@ -59,7 +66,7 @@ module Pay
59
66
  # Handles Billable#subscribe
60
67
  #
61
68
  # Returns Pay::Subscription
62
- def create_braintree_subscription(name, plan, options = {})
69
+ def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
63
70
  token = customer.payment_methods.find(&:default?).try(:token)
64
71
  raise Pay::Error, "Customer has no default payment method" if token.nil?
65
72
 
@@ -76,7 +83,7 @@ module Pay
76
83
  result = gateway.subscription.create(subscription_options)
77
84
  raise Pay::Braintree::Error, result unless result.success?
78
85
 
79
- create_subscription(result.subscription, "braintree", name, plan, status: :active)
86
+ billable.create_pay_subscription(result.subscription, "braintree", name, plan, status: :active)
80
87
  rescue ::Braintree::AuthorizationError
81
88
  raise Pay::Braintree::AuthorizationError
82
89
  rescue ::Braintree::BraintreeError => e
@@ -86,7 +93,7 @@ module Pay
86
93
  # Handles Billable#update_card
87
94
  #
88
95
  # Returns true if successful
89
- def update_braintree_card(token)
96
+ def update_card(token)
90
97
  result = gateway.payment_method.create(
91
98
  customer_id: processor_id,
92
99
  payment_method_nonce: token,
@@ -97,7 +104,7 @@ module Pay
97
104
  )
98
105
  raise Pay::Braintree::Error, result unless result.success?
99
106
 
100
- update_braintree_card_on_file result.payment_method
107
+ update_card_on_file result.payment_method
101
108
  update_subscriptions_to_payment_method(result.payment_method.token)
102
109
  true
103
110
  rescue ::Braintree::AuthorizationError
@@ -106,29 +113,25 @@ module Pay
106
113
  raise Pay::Braintree::Error, e
107
114
  end
108
115
 
109
- def update_braintree_email!
110
- braintree_customer.update(
111
- email: email,
112
- first_name: try(:first_name),
113
- last_name: try(:last_name)
114
- )
116
+ def update_email!
117
+ gateway.customer.update(processor_id, email: email, first_name: try(:first_name), last_name: try(:last_name))
115
118
  end
116
119
 
117
- def braintree_trial_end_date(subscription)
120
+ def trial_end_date(subscription)
118
121
  return unless subscription.trial_period
119
122
  # Braintree returns dates without time zones, so we'll assume they're UTC
120
123
  subscription.first_billing_date.end_of_day
121
124
  end
122
125
 
123
126
  def update_subscriptions_to_payment_method(token)
124
- subscriptions.braintree.each do |subscription|
127
+ billable.subscriptions.braintree.each do |subscription|
125
128
  if subscription.active?
126
129
  gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
127
130
  end
128
131
  end
129
132
  end
130
133
 
131
- def braintree_subscription(subscription_id, options = {})
134
+ def processor_subscription(subscription_id, options = {})
132
135
  gateway.subscription.find(subscription_id)
133
136
  end
134
137
 
@@ -140,11 +143,11 @@ module Pay
140
143
  # pass
141
144
  end
142
145
 
143
- def save_braintree_transaction(transaction)
146
+ def save_transaction(transaction)
144
147
  attrs = card_details_for_braintree_transaction(transaction)
145
148
  attrs[:amount] = transaction.amount.to_f * 100
146
149
 
147
- charge = charges.find_or_initialize_by(
150
+ charge = billable.charges.find_or_initialize_by(
148
151
  processor: :braintree,
149
152
  processor_id: transaction.id
150
153
  )
@@ -158,10 +161,10 @@ module Pay
158
161
  Pay.braintree_gateway
159
162
  end
160
163
 
161
- def update_braintree_card_on_file(payment_method)
164
+ def update_card_on_file(payment_method)
162
165
  case payment_method
163
166
  when ::Braintree::CreditCard
164
- update!(
167
+ billable.update!(
165
168
  card_type: payment_method.card_type,
166
169
  card_last4: payment_method.last_4,
167
170
  card_exp_month: payment_method.expiration_month,
@@ -169,14 +172,14 @@ module Pay
169
172
  )
170
173
 
171
174
  when ::Braintree::PayPalAccount
172
- update!(
175
+ billable.update!(
173
176
  card_type: "PayPal",
174
177
  card_last4: payment_method.email
175
178
  )
176
179
  end
177
180
 
178
181
  # Clear the card token so we don't accidentally update twice
179
- self.card_token = nil
182
+ billable.card_token = nil
180
183
  end
181
184
 
182
185
  def card_details_for_braintree_transaction(transaction)