pay 11.2.3 → 11.6.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71299748a825cda0c189c61f051f666840c413df3e33e177d126dffa9feaa2fe
4
- data.tar.gz: 3d6ad655b364d9fea688fbeae0e1f013bd6966e796559ec4cf1e2d5cb181dda0
3
+ metadata.gz: 82b0246d201ed5b493e730a2620e5ef8b084d25dbd9d2aeb45d376b3515e0500
4
+ data.tar.gz: 7086d1d8ed0c6c999fc6d9e221a4bc7bdbde9a01b07f6c2257dbe3e5ca52bf3b
5
5
  SHA512:
6
- metadata.gz: 530ea9ae04118c33c7aa91e17bb945cb752b22d175b3bdc32eef915e7e304f97f2017409eb53c3adb4ebeb319abf5e9c698ffa618167065d230c548d64ac23df
7
- data.tar.gz: a39c4b305ce5083fcf44fdd8813de11292938bd2439a6288039d0f737e6f134b0ed869c61a06b99d1db4b105207e017de805c5a6cc86cbf9c2230add37dc69da
6
+ metadata.gz: b5e2f3c52f8d2fadf7803798ffe98c9dbd2f40d91095548c87374e7a1bebab7cbe60ec18c5357fe19703fdb7761414849554dc988fc45d03c9e762f91742fff7
7
+ data.tar.gz: c32a6bd51f512eeaba0b4689a281b866b2fe246e9ee9d81b7f55491244750f8f6dfea2f400d69473096b27b117aec1e8e48e46688cace5042f9273b804561d14
data/Rakefile CHANGED
@@ -19,8 +19,6 @@ end
19
19
  APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
20
20
  load "rails/tasks/engine.rake"
21
21
 
22
- load "rails/tasks/statistics.rake"
23
-
24
22
  require "rake/testtask"
25
23
 
26
24
  Rake::TestTask.new(:test) do |t|
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class BraintreeController < Pay::ApplicationController
4
- if Rails.application.config.action_controller.default_protect_from_forgery
5
- skip_before_action :verify_authenticity_token
6
- end
7
-
3
+ class BraintreeController < ActionController::API
8
4
  def create
9
5
  queue_event(verified_event)
10
6
  head :ok
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class LemonSqueezyController < Pay::ApplicationController
4
- if Rails.application.config.action_controller.default_protect_from_forgery
5
- skip_before_action :verify_authenticity_token
6
- end
7
-
3
+ class LemonSqueezyController < ActionController::API
8
4
  def create
9
5
  if valid_signature?(request.headers["X-Signature"])
10
6
  queue_event(verify_params.as_json)
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class PaddleBillingController < Pay::ApplicationController
4
- if Rails.application.config.action_controller.default_protect_from_forgery
5
- skip_before_action :verify_authenticity_token
6
- end
7
-
3
+ class PaddleBillingController < ActionController::API
8
4
  def create
9
5
  if valid_signature?(request.headers["Paddle-Signature"])
10
6
  queue_event(verify_params.as_json)
@@ -40,7 +36,8 @@ module Pay
40
36
  digest = OpenSSL::Digest.new("sha256")
41
37
 
42
38
  hmac = OpenSSL::HMAC.hexdigest(digest, key, data)
43
- hmac == h1
39
+ return false if h1.nil? || hmac.bytesize != h1.bytesize
40
+ ActiveSupport::SecurityUtils.secure_compare(hmac, h1)
44
41
  end
45
42
 
46
43
  def verify_params
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class PaddleClassicController < Pay::ApplicationController
4
- if Rails.application.config.action_controller.default_protect_from_forgery
5
- skip_before_action :verify_authenticity_token
6
- end
7
-
3
+ class PaddleClassicController < ActionController::API
8
4
  def create
9
5
  queue_event(verified_event)
10
6
  head :ok
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module Webhooks
3
- class StripeController < Pay::ApplicationController
4
- if Rails.application.config.action_controller.default_protect_from_forgery
5
- skip_before_action :verify_authenticity_token
6
- end
7
-
3
+ class StripeController < ActionController::API
8
4
  def create
9
5
  event = verified_event
10
6
  queue_event(event) if event.livemode || Pay::Stripe.webhook_receive_test_events
@@ -2,7 +2,7 @@ module Pay
2
2
  class UserMailer < Pay.parent_mailer.constantize
3
3
  def receipt
4
4
  if params[:pay_charge].respond_to? :receipt
5
- attachments[params[:pay_charge].filename] = params[:pay_charge].receipt
5
+ attachments[params[:pay_charge].receipt_filename] = params[:pay_charge].receipt
6
6
  end
7
7
 
8
8
  mail mail_arguments
@@ -158,7 +158,7 @@ module Pay
158
158
 
159
159
  def save_payment_method(payment_method, default:)
160
160
  attributes = case payment_method
161
- when ::Braintree::CreditCard, ::Braintree::ApplePayCard, ::Braintree::GooglePayCard, ::Braintree::SamsungPayCard, ::Braintree::VisaCheckoutCard
161
+ when ::Braintree::CreditCard, ::Braintree::ApplePayCard, ::Braintree::GooglePayCard, ::Braintree::SamsungPayCard
162
162
  {
163
163
  payment_method_type: :card,
164
164
  brand: payment_method.card_type,
@@ -10,10 +10,16 @@ module Pay
10
10
  pay_customer.save_payment_method(object, default: object.default?)
11
11
  end
12
12
 
13
- # Sets payment method as default on Stripe
13
+ # Sets payment method as default
14
14
  def make_default!
15
+ return if default?
16
+
15
17
  result = gateway.customer.update(customer.processor_id, default_payment_method_token: processor_id)
16
18
  raise Pay::Braintree::Error, result unless result.success?
19
+
20
+ customer.payment_methods.update_all(default: false)
21
+ update!(default: true)
22
+
17
23
  result.success?
18
24
  end
19
25
 
@@ -2,6 +2,10 @@ module Pay
2
2
  module FakeProcessor
3
3
  class PaymentMethod < Pay::PaymentMethod
4
4
  def make_default!
5
+ return if default?
6
+
7
+ customer.payment_methods.update_all(default: false)
8
+ update!(default: true)
5
9
  end
6
10
 
7
11
  def detach
@@ -6,7 +6,7 @@ module Pay
6
6
  def self.sync_order(order_id, object: nil, try: 0, retries: 1)
7
7
  object ||= ::LemonSqueezy::Order.retrieve(id: order_id)
8
8
 
9
- pay_customer = Pay::Customer.find_by(type: "Pay::LemonSqueezy::Customer", processor_id: object.customer_id)
9
+ pay_customer = Pay::LemonSqueezy::Customer.find_by(processor_id: object.customer_id)
10
10
  return unless pay_customer
11
11
 
12
12
  processor_id = "order:#{object.id}"
@@ -34,7 +34,7 @@ module Pay
34
34
  # Skip loading the latest subscription invoice details from the API if we already have it
35
35
  object ||= ::LemonSqueezy::SubscriptionInvoice.retrieve(id: subscription_invoice_id)
36
36
 
37
- pay_customer = Pay::Customer.find_by(type: "Pay::LemonSqueezy::Customer", processor_id: object.customer_id)
37
+ pay_customer = Pay::LemonSqueezy::Customer.find_by(processor_id: object.customer_id)
38
38
  return unless pay_customer
39
39
 
40
40
  processor_id = "subscription_invoice:#{object.id}"
@@ -18,6 +18,10 @@ module Pay
18
18
  end
19
19
 
20
20
  def make_default!
21
+ return if default?
22
+
23
+ customer.payment_methods.update_all(default: false)
24
+ update!(default: true)
21
25
  end
22
26
 
23
27
  def detach
@@ -36,6 +36,10 @@ module Pay
36
36
  end
37
37
 
38
38
  def make_default!
39
+ return if default?
40
+
41
+ customer.payment_methods.update_all(default: false)
42
+ update!(default: true)
39
43
  end
40
44
 
41
45
  def detach
@@ -41,6 +41,10 @@ module Pay
41
41
  end
42
42
 
43
43
  def make_default!
44
+ return if default?
45
+
46
+ customer.payment_methods.update_all(default: false)
47
+ update!(default: true)
44
48
  end
45
49
 
46
50
  def detach
@@ -21,19 +21,6 @@ module Pay
21
21
  def self.pay_processor_for(name)
22
22
  "Pay::#{name.to_s.classify}::PaymentMethod".constantize
23
23
  end
24
-
25
- def payment_processor
26
- @payment_processor ||= self.class.pay_processor_for(customer.processor).new(self)
27
- end
28
-
29
- def make_default!
30
- return if default?
31
-
32
- payment_processor.make_default!
33
-
34
- customer.payment_methods.update_all(default: false)
35
- update!(default: true)
36
- end
37
24
  end
38
25
  end
39
26
 
@@ -48,8 +48,9 @@ module Pay
48
48
 
49
49
  # Associate charge with subscription if we can
50
50
  if object.payment_intent.present?
51
- invoice_payments = ::Stripe::InvoicePayment.list({payment: {type: :payment_intent, payment_intent: object.payment_intent}, status: :paid, expand: ["data.invoice.total_discount_amounts.discount"]}, {stripe_account: stripe_account}.compact)
52
- if invoice_payments.any? && (invoice = invoice_payments.first.invoice)
51
+ invoice_payments = ::Stripe::InvoicePayment.list({payment: {type: :payment_intent, payment_intent: object.payment_intent}, status: :paid}, {stripe_account: stripe_account}.compact)
52
+ if invoice_payments.any?
53
+ invoice = ::Stripe::Invoice.retrieve({id: invoice_payments.first.invoice, expand: ["total_discount_amounts.discount.source.coupon"]}, {stripe_account: stripe_account}.compact)
53
54
  attrs[:stripe_invoice] = invoice.to_hash
54
55
  attrs[:subtotal] = invoice.subtotal
55
56
  attrs[:tax] = invoice.total - invoice.total_excluding_tax.to_i
@@ -139,7 +140,8 @@ module Pay
139
140
  end
140
141
 
141
142
  def stripe_object
142
- ::Stripe::Charge.construct_from(object) if object?
143
+ sync! if object.nil?
144
+ ::Stripe::Charge.construct_from(object)
143
145
  end
144
146
 
145
147
  private
@@ -175,7 +175,7 @@ module Pay
175
175
  }
176
176
 
177
177
  # Hosted (the default) checkout sessions require a success_url and cancel_url
178
- if ["", "hosted"].include? options[:ui_mode].to_s
178
+ if ["", "hosted_page"].include?(options[:ui_mode].to_s)
179
179
  args[:success_url] = merge_session_id_param(options.delete(:success_url) || root_url)
180
180
  args[:cancel_url] = merge_session_id_param(options.delete(:cancel_url) || root_url)
181
181
  end
@@ -31,7 +31,7 @@ module Pay
31
31
  return
32
32
  end
33
33
 
34
- default_payment_method_id = pay_customer.api_record.invoice_settings.default_payment_method
34
+ default_payment_method_id = pay_customer.api_record.invoice_settings&.default_payment_method
35
35
  default = (id == default_payment_method_id)
36
36
 
37
37
  attributes = extract_attributes(object).merge(default: default, stripe_account: stripe_account)
@@ -67,7 +67,12 @@ module Pay
67
67
 
68
68
  # Sets payment method as default
69
69
  def make_default!
70
+ return if default?
71
+
70
72
  ::Stripe::Customer.update(customer.processor_id, {invoice_settings: {default_payment_method: processor_id}}, stripe_options)
73
+
74
+ customer.payment_methods.update_all(default: false)
75
+ update!(default: true)
71
76
  end
72
77
 
73
78
  # Remove payment method
@@ -140,6 +140,7 @@ module Pay
140
140
  end
141
141
 
142
142
  def stripe_object
143
+ sync! if object.nil?
143
144
  ::Stripe::Subscription.construct_from(object)
144
145
  end
145
146
 
@@ -279,7 +280,6 @@ module Pay
279
280
  unpause
280
281
  else
281
282
  @api_record = ::Stripe::Subscription.update(processor_id, {
282
- plan: processor_plan,
283
283
  trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
284
284
  cancel_at_period_end: false
285
285
  }.merge(expand_options),
@@ -300,10 +300,9 @@ module Pay
300
300
  processor_id,
301
301
  {
302
302
  cancel_at_period_end: false,
303
- plan: plan,
303
+ items: [{id: subscription_items.first.id, plan: plan, quantity: quantity}],
304
304
  proration_behavior: proration_behavior,
305
- trial_end: (on_trial? ? trial_ends_at.to_i : "now"),
306
- quantity: quantity
305
+ trial_end: (on_trial? ? trial_ends_at.to_i : "now")
307
306
  }.merge(expand_options).merge(options),
308
307
  stripe_options
309
308
  )
@@ -335,7 +334,7 @@ module Pay
335
334
 
336
335
  # Retries the latest invoice for a Past Due subscription and attempts to pay it
337
336
  def retry_failed_payment(payment_intent_id: nil)
338
- payment_intent_id ||= api_record.latest_invoice.payment_intent.id
337
+ payment_intent_id ||= api_record.latest_invoice.payments.first.payment.payment_intent
339
338
  payment_intent = ::Stripe::PaymentIntent.retrieve({id: payment_intent_id}, stripe_options)
340
339
 
341
340
  payment_intent = if payment_intent.status == "requires_payment_method"
@@ -3,7 +3,7 @@ module Pay
3
3
  STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid paused]
4
4
 
5
5
  # Associations
6
- belongs_to :customer
6
+ belongs_to :customer, touch: true
7
7
  belongs_to :payment_method, optional: true, primary_key: :processor_id
8
8
  has_many :charges
9
9
 
@@ -37,7 +37,11 @@ module Pay
37
37
 
38
38
  with_lock do
39
39
  pay_customers.update_all(default: false)
40
- pay_customer = pay_customers.active.where(processor: processor_name, type: klass.name, stripe_account: stripe_account).first_or_initialize
40
+ pay_customer = pay_customers.active.where(
41
+ Pay::Customer.inheritance_column => klass.name,
42
+ :processor => processor_name,
43
+ :stripe_account => stripe_account
44
+ ).first_or_initialize
41
45
  pay_customer.update!(attributes.merge(default: true))
42
46
  end
43
47
 
@@ -52,7 +56,11 @@ module Pay
52
56
  klass = "Pay::#{processor_name.to_s.classify}::Customer".constantize
53
57
  raise ArgumentError, "not a valid payment processor" if klass.ancestors.exclude?(Pay::Customer)
54
58
 
55
- pay_customer = pay_customers.active.where(processor: processor_name, type: klass.name, stripe_account: stripe_account).first_or_initialize
59
+ pay_customer = pay_customers.active.where(
60
+ Pay::Customer.inheritance_column => klass.name,
61
+ :processor => processor_name,
62
+ :stripe_account => stripe_account
63
+ ).first_or_initialize
56
64
  pay_customer.update!(attributes)
57
65
  pay_customer
58
66
  end
@@ -83,7 +91,10 @@ module Pay
83
91
  def set_merchant_processor(processor_name, **attributes)
84
92
  with_lock do
85
93
  pay_merchants.update_all(default: false)
86
- pay_merchant = pay_merchants.where(processor: processor_name, type: "Pay::#{processor_name.to_s.classify}::Merchant").first_or_initialize
94
+ pay_merchant = pay_merchants.where(
95
+ Pay::Merchant.inheritance_column => "Pay::#{processor_name.to_s.classify}::Merchant",
96
+ :processor => processor_name
97
+ ).first_or_initialize
87
98
  pay_merchant.update!(attributes.merge(default: true))
88
99
  end
89
100
 
data/lib/pay/errors.rb CHANGED
@@ -3,7 +3,7 @@ module Pay
3
3
  class Error < StandardError
4
4
  end
5
5
 
6
- class PaymentError < StandardError
6
+ class PaymentError < Error
7
7
  attr_reader :payment
8
8
 
9
9
  def initialize(payment)
@@ -39,7 +39,7 @@ module Pay
39
39
 
40
40
  # verify the data
41
41
  digest = OpenSSL::Digest.new("SHA1")
42
- pub_key = OpenSSL::PKey::RSA.new(public_key)
42
+ pub_key = OpenSSL::PKey::RSA.new(public_key).public_key
43
43
  pub_key.verify(digest, signature, data_serialized)
44
44
  end
45
45
 
data/lib/pay/receipts.rb CHANGED
@@ -70,8 +70,8 @@ module Pay
70
70
  items
71
71
  end
72
72
 
73
- def discount_description(discount)
74
- coupon = discount.discount.coupon
73
+ def discount_description(total_discount_amount)
74
+ coupon = total_discount_amount.discount.try(:source).try(:coupon) || total_discount_amount.discount.try(:coupon)
75
75
  name = coupon.name
76
76
 
77
77
  if (percent = coupon.percent_off)
@@ -0,0 +1,34 @@
1
+ module Pay
2
+ module Stripe
3
+ module Webhooks
4
+ class InvoiceUpdated
5
+ def call(event)
6
+ # Grab the invoice from the event
7
+ invoice = event.data.object
8
+
9
+ # Find the corresponding subscription_id
10
+ subscription_id = invoice.parent.try(:subscription_details).try(:subscription)
11
+
12
+ # Not all invoices have a subscription - could be a one-time or manual payment
13
+ return if subscription_id.blank?
14
+
15
+ # Grab the local subscription
16
+ subscription = Pay::Subscription.find_by_processor_and_id(:stripe, subscription_id)
17
+
18
+ # Return if we don't have a corresponding subscription
19
+ return unless subscription
20
+
21
+ # Grab the local latest_invoice from the subscription stripe_object
22
+ latest_invoice = subscription.stripe_object.try(:latest_invoice)
23
+
24
+ # Compare the local invoice id to the event invoice id and sync if they are the same
25
+ if latest_invoice.id.to_s == invoice.id.to_s
26
+ Pay::Stripe::Subscription.sync(subscription_id, stripe_account: event.try(:account))
27
+ end
28
+ rescue ::Stripe::StripeError => e
29
+ Rails.logger.error "Stripe Webhook Error (InvoiceUpdated): #{e.message}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/pay/stripe.rb CHANGED
@@ -12,6 +12,7 @@ module Pay
12
12
  autoload :CheckoutSessionAsyncPaymentSucceeded, "pay/stripe/webhooks/checkout_session_async_payment_succeeded"
13
13
  autoload :CustomerDeleted, "pay/stripe/webhooks/customer_deleted"
14
14
  autoload :CustomerUpdated, "pay/stripe/webhooks/customer_updated"
15
+ autoload :InvoiceUpdated, "pay/stripe/webhooks/invoice_updated"
15
16
  autoload :PaymentActionRequired, "pay/stripe/webhooks/payment_action_required"
16
17
  autoload :PaymentFailed, "pay/stripe/webhooks/payment_failed"
17
18
  autoload :PaymentIntentSucceeded, "pay/stripe/webhooks/payment_intent_succeeded"
@@ -27,7 +28,7 @@ module Pay
27
28
 
28
29
  extend Env
29
30
 
30
- REQUIRED_VERSION = "~> 15"
31
+ REQUIRED_VERSION = "~> 19"
31
32
 
32
33
  # A list of database model names that include Pay
33
34
  # Used for safely looking up models with client_reference_id
@@ -84,6 +85,9 @@ module Pay
84
85
  # This probably should be ignored for monthly subscriptions.
85
86
  events.subscribe "stripe.invoice.upcoming", Pay::Stripe::Webhooks::SubscriptionRenewing.new
86
87
 
88
+ # Ensures the local stripe_object is up-to-date anytime the API's record is updated.
89
+ events.subscribe "stripe.invoice.updated", Pay::Stripe::Webhooks::InvoiceUpdated.new
90
+
87
91
  # Payment action is required to process an invoice
88
92
  events.subscribe "stripe.invoice.payment_action_required", Pay::Stripe::Webhooks::PaymentActionRequired.new
89
93
 
@@ -111,6 +115,7 @@ module Pay
111
115
  # If a customer's payment source was deleted in Stripe, we should update as well
112
116
  events.subscribe "stripe.payment_method.attached", Pay::Stripe::Webhooks::PaymentMethodAttached.new
113
117
  events.subscribe "stripe.payment_method.updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
118
+ events.subscribe "stripe.payment_method.automatically_updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
114
119
  events.subscribe "stripe.payment_method.card_automatically_updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
115
120
  events.subscribe "stripe.payment_method.detached", Pay::Stripe::Webhooks::PaymentMethodDetached.new
116
121
 
data/lib/pay/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pay
2
- VERSION = "11.2.3"
2
+ VERSION = "11.6.2"
3
3
  end
data/lib/pay.rb CHANGED
@@ -38,7 +38,7 @@ module Pay
38
38
  mattr_accessor :business_address
39
39
  mattr_accessor :business_name
40
40
  mattr_accessor :business_logo
41
- mattr_accessor :support_email
41
+ mattr_reader :support_email
42
42
 
43
43
  def self.support_email=(value)
44
44
  @@support_email = value.is_a?(::Mail::Address) ? value : ::Mail::Address.new(value)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pay
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.2.3
4
+ version: 11.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Charnes
@@ -153,6 +153,7 @@ files:
153
153
  - lib/pay/stripe/webhooks/checkout_session_completed.rb
154
154
  - lib/pay/stripe/webhooks/customer_deleted.rb
155
155
  - lib/pay/stripe/webhooks/customer_updated.rb
156
+ - lib/pay/stripe/webhooks/invoice_updated.rb
156
157
  - lib/pay/stripe/webhooks/payment_action_required.rb
157
158
  - lib/pay/stripe/webhooks/payment_failed.rb
158
159
  - lib/pay/stripe/webhooks/payment_intent_succeeded.rb
@@ -172,7 +173,8 @@ files:
172
173
  homepage: https://github.com/pay-rails/pay
173
174
  licenses:
174
175
  - MIT
175
- metadata: {}
176
+ metadata:
177
+ rubygems_mfa_required: 'true'
176
178
  rdoc_options: []
177
179
  require_paths:
178
180
  - lib
@@ -187,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
189
  - !ruby/object:Gem::Version
188
190
  version: '0'
189
191
  requirements: []
190
- rubygems_version: 3.7.1
192
+ rubygems_version: 4.0.15
191
193
  specification_version: 4
192
194
  summary: Payments engine for Ruby on Rails
193
195
  test_files: []