pay 7.2.1 → 8.0.0

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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/app/controllers/pay/webhooks/lemon_squeezy_controller.rb +45 -0
  4. data/app/controllers/pay/webhooks/stripe_controller.rb +2 -1
  5. data/app/jobs/pay/customer_sync_job.rb +1 -1
  6. data/app/models/concerns/pay/routing.rb +13 -0
  7. data/{lib → app/models}/pay/braintree/charge.rb +5 -12
  8. data/{lib/pay/braintree/billable.rb → app/models/pay/braintree/customer.rb} +31 -71
  9. data/{lib → app/models}/pay/braintree/payment_method.rb +1 -9
  10. data/{lib → app/models}/pay/braintree/subscription.rb +14 -52
  11. data/app/models/pay/charge.rb +8 -27
  12. data/app/models/pay/customer.rb +2 -15
  13. data/app/models/pay/fake_processor/charge.rb +13 -0
  14. data/{lib/pay/fake_processor/billable.rb → app/models/pay/fake_processor/customer.rb} +22 -35
  15. data/{lib → app/models}/pay/fake_processor/merchant.rb +2 -9
  16. data/app/models/pay/fake_processor/payment_method.rb +11 -0
  17. data/app/models/pay/fake_processor/subscription.rb +60 -0
  18. data/app/models/pay/lemon_squeezy/charge.rb +86 -0
  19. data/app/models/pay/lemon_squeezy/customer.rb +78 -0
  20. data/app/models/pay/lemon_squeezy/payment_method.rb +27 -0
  21. data/app/models/pay/lemon_squeezy/subscription.rb +129 -0
  22. data/app/models/pay/merchant.rb +0 -11
  23. data/{lib → app/models}/pay/paddle_billing/charge.rb +2 -8
  24. data/{lib/pay/paddle_billing/billable.rb → app/models/pay/paddle_billing/customer.rb} +18 -35
  25. data/{lib → app/models}/pay/paddle_billing/payment_method.rb +2 -12
  26. data/{lib → app/models}/pay/paddle_billing/subscription.rb +9 -33
  27. data/{lib → app/models}/pay/paddle_classic/charge.rb +13 -18
  28. data/{lib/pay/paddle_classic/billable.rb → app/models/pay/paddle_classic/customer.rb} +9 -31
  29. data/{lib → app/models}/pay/paddle_classic/payment_method.rb +1 -11
  30. data/{lib → app/models}/pay/paddle_classic/subscription.rb +11 -36
  31. data/app/models/pay/payment_method.rb +0 -5
  32. data/{lib → app/models}/pay/stripe/charge.rb +6 -22
  33. data/{lib/pay/stripe/billable.rb → app/models/pay/stripe/customer.rb} +73 -108
  34. data/{lib → app/models}/pay/stripe/merchant.rb +2 -11
  35. data/{lib → app/models}/pay/stripe/payment_method.rb +2 -10
  36. data/{lib → app/models}/pay/stripe/subscription.rb +37 -71
  37. data/app/models/pay/subscription.rb +7 -37
  38. data/app/models/pay/webhook.rb +2 -0
  39. data/config/routes.rb +1 -0
  40. data/db/migrate/2_add_pay_sti_columns.rb +24 -0
  41. data/lib/pay/attributes.rb +11 -3
  42. data/lib/pay/braintree.rb +25 -6
  43. data/lib/pay/engine.rb +2 -0
  44. data/lib/pay/fake_processor.rb +2 -6
  45. data/lib/pay/lemon_squeezy/webhooks/order.rb +11 -0
  46. data/lib/pay/lemon_squeezy/webhooks/subscription.rb +3 -3
  47. data/lib/pay/lemon_squeezy/webhooks/subscription_payment.rb +11 -0
  48. data/lib/pay/lemon_squeezy.rb +56 -104
  49. data/lib/pay/paddle_billing.rb +15 -6
  50. data/lib/pay/paddle_classic.rb +11 -9
  51. data/lib/pay/receipts.rb +6 -6
  52. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +1 -1
  53. data/lib/pay/stripe/webhooks/customer_updated.rb +1 -1
  54. data/lib/pay/stripe/webhooks/subscription_trial_will_end.rb +1 -1
  55. data/lib/pay/stripe.rb +21 -7
  56. data/lib/pay/version.rb +1 -1
  57. data/lib/pay.rb +12 -1
  58. metadata +34 -38
  59. data/app/views/pay/stripe/_checkout_button.html.erb +0 -21
  60. data/lib/pay/braintree/authorization_error.rb +0 -9
  61. data/lib/pay/braintree/error.rb +0 -23
  62. data/lib/pay/fake_processor/charge.rb +0 -21
  63. data/lib/pay/fake_processor/error.rb +0 -6
  64. data/lib/pay/fake_processor/payment_method.rb +0 -21
  65. data/lib/pay/fake_processor/subscription.rb +0 -90
  66. data/lib/pay/lemon_squeezy/billable.rb +0 -90
  67. data/lib/pay/lemon_squeezy/charge.rb +0 -68
  68. data/lib/pay/lemon_squeezy/error.rb +0 -7
  69. data/lib/pay/lemon_squeezy/payment_method.rb +0 -40
  70. data/lib/pay/lemon_squeezy/subscription.rb +0 -185
  71. data/lib/pay/lemon_squeezy/webhooks/transaction_completed.rb +0 -11
  72. data/lib/pay/paddle_billing/error.rb +0 -7
  73. data/lib/pay/paddle_classic/error.rb +0 -7
  74. data/lib/pay/stripe/error.rb +0 -7
@@ -0,0 +1,24 @@
1
+ class AddPayStiColumns < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :pay_customers, :type, :string
4
+ add_column :pay_charges, :type, :string
5
+ add_column :pay_subscriptions, :type, :string
6
+
7
+ rename_column :pay_payment_methods, :type, :payment_method_type
8
+ add_column :pay_payment_methods, :type, :string
9
+
10
+ add_column :pay_merchants, :type, :string
11
+
12
+ Pay::Customer.find_each do |pay_customer|
13
+ pay_customer.update(type: "Pay::#{pay_customer.processor.classify}::Customer")
14
+
15
+ pay_customer.charges.update_all(type: "Pay::#{pay_customer.processor.classify}::Charge")
16
+ pay_customer.subscriptions.update_all(type: "Pay::#{pay_customer.processor.classify}::Subscription")
17
+ pay_customer.payment_methods.update_all(type: "Pay::#{pay_customer.processor.classify}::PaymentMethod")
18
+ end
19
+
20
+ Pay::Merchant.find_each do |pay_merchant|
21
+ pay_merchant.update(type: "Pay::#{pay_merchant.processor.classify}::Merchant")
22
+ end
23
+ end
24
+ end
@@ -31,9 +31,13 @@ module Pay
31
31
  def set_payment_processor(processor_name, allow_fake: false, **attributes)
32
32
  raise Pay::Error, "Processor `#{processor_name}` is not allowed" if processor_name.to_s == "fake_processor" && !allow_fake
33
33
 
34
+ # Safety check to make sure this is a valid Pay processor
35
+ klass = "Pay::#{processor_name.to_s.classify}::Customer".constantize
36
+ raise ArgumentError, "not a valid payment processor" if klass.ancestors.exclude?(Pay::Customer)
37
+
34
38
  ActiveRecord::Base.transaction do
35
39
  pay_customers.update_all(default: false)
36
- pay_customer = pay_customers.active.where(processor: processor_name).first_or_initialize
40
+ pay_customer = pay_customers.active.where(processor: processor_name, type: klass.name).first_or_initialize
37
41
  pay_customer.update!(attributes.merge(default: true))
38
42
  end
39
43
 
@@ -44,7 +48,11 @@ module Pay
44
48
  def add_payment_processor(processor_name, allow_fake: false, **attributes)
45
49
  raise Pay::Error, "Processor `#{processor_name}` is not allowed" if processor_name.to_s == "fake_processor" && !allow_fake
46
50
 
47
- pay_customer = pay_customers.active.where(processor: processor_name).first_or_initialize
51
+ # Safety check to make sure this is a valid Pay processor
52
+ klass = "Pay::#{processor_name.to_s.classify}::Customer".constantize
53
+ raise ArgumentError, "not a valid payment processor" if klass.ancestors.exclude?(Pay::Customer)
54
+
55
+ pay_customer = pay_customers.active.where(processor: processor_name, type: klass.name).first_or_initialize
48
56
  pay_customer.update!(attributes)
49
57
  pay_customer
50
58
  end
@@ -75,7 +83,7 @@ module Pay
75
83
  def set_merchant_processor(processor_name, **attributes)
76
84
  ActiveRecord::Base.transaction do
77
85
  pay_merchants.update_all(default: false)
78
- pay_merchant = pay_merchants.where(processor: processor_name).first_or_initialize
86
+ pay_merchant = pay_merchants.where(processor: processor_name, type: "Pay::#{processor_name.to_s.classify}::Merchant").first_or_initialize
79
87
  pay_merchant.update!(attributes.merge(default: true))
80
88
  end
81
89
 
data/lib/pay/braintree.rb CHANGED
@@ -1,11 +1,30 @@
1
1
  module Pay
2
2
  module Braintree
3
- autoload :AuthorizationError, "pay/braintree/authorization_error"
4
- autoload :Billable, "pay/braintree/billable"
5
- autoload :Charge, "pay/braintree/charge"
6
- autoload :Error, "pay/braintree/error"
7
- autoload :PaymentMethod, "pay/braintree/payment_method"
8
- autoload :Subscription, "pay/braintree/subscription"
3
+ class Error < Pay::Error
4
+ # For any manually raised Braintree error results (for failure responses)
5
+ # we can raise this exception manually but treat it as if we wrapped an exception
6
+
7
+ attr_reader :result
8
+
9
+ def initialize(result)
10
+ if result.is_a?(::Braintree::ErrorResult)
11
+ super(result.message)
12
+ @result = result
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def cause
19
+ super || result
20
+ end
21
+ end
22
+
23
+ class AuthorizationError < Error
24
+ def message
25
+ I18n.t("pay.errors.braintree.authorization")
26
+ end
27
+ end
9
28
 
10
29
  module Webhooks
11
30
  autoload :SubscriptionCanceled, "pay/braintree/webhooks/subscription_canceled"
data/lib/pay/engine.rb CHANGED
@@ -28,12 +28,14 @@ module Pay
28
28
  Pay::Braintree.configure_webhooks if Pay::Braintree.enabled?
29
29
  Pay::PaddleBilling.configure_webhooks if Pay::PaddleBilling.enabled?
30
30
  Pay::PaddleClassic.configure_webhooks if Pay::PaddleClassic.enabled?
31
+ Pay::LemonSqueezy.configure_webhooks if Pay::LemonSqueezy.enabled?
31
32
  end
32
33
 
33
34
  config.to_prepare do
34
35
  Pay::Stripe.setup if Pay::Stripe.enabled?
35
36
  Pay::Braintree.setup if Pay::Braintree.enabled?
36
37
  Pay::PaddleBilling.setup if Pay::PaddleBilling.enabled?
38
+ Pay::LemonSqueezy.setup if Pay::LemonSqueezy.enabled?
37
39
 
38
40
  if defined?(::Receipts::VERSION)
39
41
  if Pay::Engine.version_matches?(required: "~> 2", current: ::Receipts::VERSION)
@@ -1,10 +1,6 @@
1
1
  module Pay
2
2
  module FakeProcessor
3
- autoload :Billable, "pay/fake_processor/billable"
4
- autoload :Charge, "pay/fake_processor/charge"
5
- autoload :Error, "pay/fake_processor/error"
6
- autoload :PaymentMethod, "pay/fake_processor/payment_method"
7
- autoload :Subscription, "pay/fake_processor/subscription"
8
- autoload :Merchant, "pay/fake_processor/merchant"
3
+ class Error < Pay::Error
4
+ end
9
5
  end
10
6
  end
@@ -0,0 +1,11 @@
1
+ module Pay
2
+ module LemonSqueezy
3
+ module Webhooks
4
+ class Order
5
+ def call(order)
6
+ Pay::LemonSqueezy.sync_order(order.id, object: order)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,9 +1,9 @@
1
1
  module Pay
2
- module PaddleBilling
2
+ module LemonSqueezy
3
3
  module Webhooks
4
4
  class Subscription
5
- def call(event)
6
- Pay::PaddleBilling::Subscription.sync(event.id, object: event)
5
+ def call(subscription)
6
+ Pay::LemonSqueezy::Subscription.sync(subscription.id, object: subscription)
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,11 @@
1
+ module Pay
2
+ module LemonSqueezy
3
+ module Webhooks
4
+ class SubscriptionPayment
5
+ def call(subscription_invoice)
6
+ Pay::LemonSqueezy::Charge.sync_subscription_invoice(subscription_invoice.id, object: subscription_invoice)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,138 +1,90 @@
1
1
  module Pay
2
2
  module LemonSqueezy
3
- autoload :Billable, "pay/stripe/billable"
4
- autoload :Charge, "pay/stripe/charge"
5
- autoload :Error, "pay/stripe/error"
6
- autoload :Merchant, "pay/stripe/merchant"
7
- autoload :PaymentMethod, "pay/stripe/payment_method"
8
- autoload :Subscription, "pay/stripe/subscription"
3
+ class Error < Pay::Error
4
+ delegate :message, to: :cause
5
+ end
9
6
 
10
7
  module Webhooks
11
- autoload :AccountUpdated, "pay/stripe/webhooks/account_updated"
12
- autoload :ChargeRefunded, "pay/stripe/webhooks/charge_refunded"
13
- autoload :ChargeSucceeded, "pay/stripe/webhooks/charge_succeeded"
14
- autoload :CheckoutSessionCompleted, "pay/stripe/webhooks/checkout_session_completed"
15
- autoload :CheckoutSessionAsyncPaymentSucceeded, "pay/stripe/webhooks/checkout_session_async_payment_succeeded"
16
- autoload :CustomerDeleted, "pay/stripe/webhooks/customer_deleted"
17
- autoload :CustomerUpdated, "pay/stripe/webhooks/customer_updated"
18
- autoload :PaymentActionRequired, "pay/stripe/webhooks/payment_action_required"
19
- autoload :PaymentFailed, "pay/stripe/webhooks/payment_failed"
20
- autoload :PaymentIntentSucceeded, "pay/stripe/webhooks/payment_intent_succeeded"
21
- autoload :PaymentMethodAttached, "pay/stripe/webhooks/payment_method_attached"
22
- autoload :PaymentMethodDetached, "pay/stripe/webhooks/payment_method_detached"
23
- autoload :PaymentMethodUpdated, "pay/stripe/webhooks/payment_method_updated"
24
- autoload :SubscriptionCreated, "pay/stripe/webhooks/subscription_created"
25
- autoload :SubscriptionDeleted, "pay/stripe/webhooks/subscription_deleted"
26
- autoload :SubscriptionRenewing, "pay/stripe/webhooks/subscription_renewing"
27
- autoload :SubscriptionUpdated, "pay/stripe/webhooks/subscription_updated"
28
- autoload :SubscriptionTrialWillEnd, "pay/stripe/webhooks/subscription_trial_will_end"
8
+ autoload :Order, "pay/lemon_squeezy/webhooks/order"
9
+ autoload :Subscription, "pay/lemon_squeezy/webhooks/subscription"
10
+ autoload :SubscriptionPayment, "pay/lemon_squeezy/webhooks/subscription_payment"
29
11
  end
30
12
 
31
13
  extend Env
32
14
 
33
- REQUIRED_VERSION = "~> 1"
34
-
35
15
  def self.enabled?
36
- return false unless Pay.enabled_processors.include?(:lemonsqueezy) && defined?(::Lemonzsqueezy)
16
+ return false unless Pay.enabled_processors.include?(:lemon_squeezy) && defined?(::LemonSqueezy)
37
17
 
38
- Pay::Engine.version_matches?(required: REQUIRED_VERSION, current: ::Lemonsqueezy::VERSION) || (raise "[Pay] lemonsqueezy gem must be version #{REQUIRED_VERSION}")
18
+ Pay::Engine.version_matches?(required: "~> 1.0", current: ::LemonSqueezy::VERSION) || (raise "[Pay] lemonsqueezy gem must be version ~> 1.0")
39
19
  end
40
20
 
41
21
  def self.setup
42
- ::Stripe.api_key = private_key
22
+ ::LemonSqueezy.config.api_key = api_key
23
+ end
43
24
 
44
- # Used by Stripe to identify Pay for support
45
- ::Stripe.set_app_info("PayRails", partner_id: "pp_partner_IqhY0UExnJYLxg", version: Pay::VERSION, url: "https://github.com/pay-rails/pay")
25
+ def self.api_key
26
+ find_value_by_name(:lemon_squeezy, :api_key)
27
+ end
46
28
 
47
- # Automatically retry requests that fail
48
- # This automatically includes idempotency keys in the request to guarantee that retires are safe
49
- # https://github.com/stripe/stripe-ruby#configuring-automatic-retries
50
- ::Stripe.max_network_retries = 2
29
+ def self.store_id
30
+ find_value_by_name(:lemon_squeezy, :store_id)
51
31
  end
52
32
 
53
- def self.public_key
54
- find_value_by_name(:stripe, :public_key)
33
+ def self.signing_secret
34
+ find_value_by_name(:lemon_squeezy, :signing_secret)
55
35
  end
56
36
 
57
- def self.private_key
58
- find_value_by_name(:stripe, :private_key)
37
+ def self.passthrough(owner:, **options)
38
+ owner.to_sgid.to_s
59
39
  end
60
40
 
61
- def self.signing_secret
62
- find_value_by_name(:stripe, :signing_secret)
41
+ def self.owner_from_passthrough(passthrough)
42
+ GlobalID::Locator.locate_signed passthrough
43
+ rescue JSON::ParserError
44
+ nil
63
45
  end
64
46
 
65
47
  def self.configure_webhooks
66
48
  Pay::Webhooks.configure do |events|
67
- # Listen to the charge event to make sure we get non-subscription
68
- # purchases as well. Invoice is only for subscriptions and manual creation
69
- # so it does not include individual charges.
70
- events.subscribe "stripe.charge.succeeded", Pay::Stripe::Webhooks::ChargeSucceeded.new
71
- events.subscribe "stripe.charge.refunded", Pay::Stripe::Webhooks::ChargeRefunded.new
72
-
73
- events.subscribe "stripe.payment_intent.succeeded", Pay::Stripe::Webhooks::PaymentIntentSucceeded.new
74
-
75
- # Warn user of upcoming charges for their subscription. This is handy for
76
- # notifying annual users their subscription will renew shortly.
77
- # This probably should be ignored for monthly subscriptions.
78
- events.subscribe "stripe.invoice.upcoming", Pay::Stripe::Webhooks::SubscriptionRenewing.new
79
-
80
- # Payment action is required to process an invoice
81
- events.subscribe "stripe.invoice.payment_action_required", Pay::Stripe::Webhooks::PaymentActionRequired.new
82
-
83
- # If an invoice payment fails, we want to notify the user via email to update their payment details
84
- events.subscribe "stripe.invoice.payment_failed", Pay::Stripe::Webhooks::PaymentFailed.new
85
-
86
- # If a subscription is manually created on Stripe, we want to sync
87
- events.subscribe "stripe.customer.subscription.created", Pay::Stripe::Webhooks::SubscriptionCreated.new
88
-
89
- # If the plan, quantity, or trial ending date is updated on Stripe, we want to sync
90
- events.subscribe "stripe.customer.subscription.updated", Pay::Stripe::Webhooks::SubscriptionUpdated.new
91
-
92
- # When a customers subscription is canceled, we want to update our records
93
- events.subscribe "stripe.customer.subscription.deleted", Pay::Stripe::Webhooks::SubscriptionDeleted.new
94
-
95
- # When a customers subscription trial period is 3 days from ending or ended immediately this event is fired
96
- events.subscribe "stripe.customer.subscription.trial_will_end", Pay::Stripe::Webhooks::SubscriptionTrialWillEnd.new
97
-
98
- # Monitor changes for customer's default card changing and invoice credit updates
99
- events.subscribe "stripe.customer.updated", Pay::Stripe::Webhooks::CustomerUpdated.new
100
-
101
- # If a customer was deleted in Stripe, their subscriptions should be cancelled
102
- events.subscribe "stripe.customer.deleted", Pay::Stripe::Webhooks::CustomerDeleted.new
103
-
104
- # If a customer's payment source was deleted in Stripe, we should update as well
105
- events.subscribe "stripe.payment_method.attached", Pay::Stripe::Webhooks::PaymentMethodAttached.new
106
- events.subscribe "stripe.payment_method.updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
107
- events.subscribe "stripe.payment_method.card_automatically_updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
108
- events.subscribe "stripe.payment_method.detached", Pay::Stripe::Webhooks::PaymentMethodDetached.new
109
-
110
- # If an account is updated in stripe, we should update it as well
111
- events.subscribe "stripe.account.updated", Pay::Stripe::Webhooks::AccountUpdated.new
112
-
113
- # Handle subscriptions in Stripe Checkout Sessions
114
- events.subscribe "stripe.checkout.session.completed", Pay::Stripe::Webhooks::CheckoutSessionCompleted.new
115
- events.subscribe "stripe.checkout.session.async_payment_succeeded", Pay::Stripe::Webhooks::CheckoutSessionAsyncPaymentSucceeded.new
49
+ events.subscribe "lemon_squeezy.order_created", Pay::LemonSqueezy::Webhooks::Order.new
50
+ events.subscribe "lemon_squeezy.subscription_created", Pay::LemonSqueezy::Webhooks::Subscription.new
51
+ events.subscribe "lemon_squeezy.subscription_updated", Pay::LemonSqueezy::Webhooks::Subscription.new
52
+ events.subscribe "lemon_squeezy.subscription_payment_refunded", Pay::LemonSqueezy::Webhooks::SubscriptionPayment.new
53
+ events.subscribe "lemon_squeezy.subscription_payment_success", Pay::LemonSqueezy::Webhooks::SubscriptionPayment.new
116
54
  end
117
55
  end
118
56
 
119
- def self.to_client_reference_id(record)
120
- raise ArgumentError, "#{record.class.name} does not include Pay. Allowed models: #{model_names.to_a.join(", ")}" unless model_names.include?(record.class.name)
121
- [record.class.name, record.id].join("_")
57
+ def self.construct_from_webhook_event(event)
58
+ data = event["data"]
59
+ case data
60
+ when Array
61
+ data.map do |object|
62
+ construct_from_webhook_event(object)
63
+ end
64
+ when Hash
65
+ type = {
66
+ "orders" => ::LemonSqueezy::Order,
67
+ "subscriptions" => ::LemonSqueezy::Subscription,
68
+ "subscription-invoices" => ::LemonSqueezy::SubscriptionInvoice
69
+ }.fetch(data["type"])
70
+
71
+ type.new(data)
72
+ end
122
73
  end
123
74
 
124
- def self.find_by_client_reference_id(client_reference_id)
125
- # If there is a client reference ID, make sure we have a Pay::Customer record
126
- # client_reference_id should be in the format of "User/1"
127
- model_name, id = client_reference_id.split("_", 2)
128
-
129
- # Only allow model names that use Pay
130
- return unless model_names.include?(model_name)
75
+ # An Order may have subscriptions or be a one-time purchase
76
+ def self.sync_order(order_id, object: nil)
77
+ subscriptions = ::LemonSqueezy::Subscription.list(order_id: order_id).data
78
+ subscriptions.each do |subscription|
79
+ Pay::LemonSqueezy::Subscription.sync(subscription.id, object: subscription)
80
+ ::LemonSqueezy::SubscriptionInvoice.list(subscription_id: subscription.id).data.each do |invoice|
81
+ Pay::LemonSqueezy::Charge.sync_subscription_invoice(invoice.id, object: invoice)
82
+ end
83
+ end
131
84
 
132
- model_name.constantize.find(id)
133
- rescue ActiveRecord::RecordNotFound
134
- Rails.logger.error "[Pay] Unable to locate record with: #{client_reference_id}"
135
- nil
85
+ if subscriptions.empty?
86
+ Pay::LemonSqueezy::Charge.sync_order(order_id, object: object)
87
+ end
136
88
  end
137
89
  end
138
90
  end
@@ -1,10 +1,8 @@
1
1
  module Pay
2
2
  module PaddleBilling
3
- autoload :Billable, "pay/paddle_billing/billable"
4
- autoload :Charge, "pay/paddle_billing/charge"
5
- autoload :Error, "pay/paddle_billing/error"
6
- autoload :PaymentMethod, "pay/paddle_billing/payment_method"
7
- autoload :Subscription, "pay/paddle_billing/subscription"
3
+ class Error < Pay::Error
4
+ delegate :message, to: :cause
5
+ end
8
6
 
9
7
  module Webhooks
10
8
  autoload :Subscription, "pay/paddle_billing/webhooks/subscription"
@@ -16,7 +14,8 @@ module Pay
16
14
  def self.enabled?
17
15
  return false unless Pay.enabled_processors.include?(:paddle_billing) && defined?(::Paddle)
18
16
 
19
- Pay::Engine.version_matches?(required: "~> 2.1", current: ::Paddle::VERSION) || (raise "[Pay] paddle gem must be version ~> 2.1")
17
+ Pay::Engine.version_matches?(required: "~> 2.5",
18
+ current: ::Paddle::VERSION) || (raise "[Pay] paddle gem must be version ~> 2.5")
20
19
  end
21
20
 
22
21
  def self.setup
@@ -54,5 +53,15 @@ module Pay
54
53
  events.subscribe "paddle_billing.transaction.completed", Pay::PaddleBilling::Webhooks::TransactionCompleted.new
55
54
  end
56
55
  end
56
+
57
+ def self.sync_transaction(transaction_id)
58
+ transaction = ::Paddle::Transaction.retrieve(id: transaction_id)
59
+
60
+ if transaction.subscription_id.present?
61
+ Pay::PaddleBilling::Subscription.sync(transaction.subscription_id)
62
+ else
63
+ Pay::PaddleBilling::Charge.sync(transaction_id, object: transaction)
64
+ end
65
+ end
57
66
  end
58
67
  end
@@ -1,10 +1,8 @@
1
1
  module Pay
2
2
  module PaddleClassic
3
- autoload :Billable, "pay/paddle_classic/billable"
4
- autoload :Charge, "pay/paddle_classic/charge"
5
- autoload :Error, "pay/paddle_classic/error"
6
- autoload :PaymentMethod, "pay/paddle_classic/payment_method"
7
- autoload :Subscription, "pay/paddle_classic/subscription"
3
+ class Error < Pay::Error
4
+ delegate :message, to: :cause
5
+ end
8
6
 
9
7
  module Webhooks
10
8
  autoload :SignatureVerifier, "pay/paddle_classic/webhooks/signature_verifier"
@@ -20,7 +18,8 @@ module Pay
20
18
  def self.enabled?
21
19
  return false unless Pay.enabled_processors.include?(:paddle_classic) && defined?(::Paddle)
22
20
 
23
- Pay::Engine.version_matches?(required: "~> 2.1", current: ::Paddle::VERSION) || (raise "[Pay] paddle gem must be version ~> 2.1")
21
+ Pay::Engine.version_matches?(required: "~> 2.5",
22
+ current: ::Paddle::VERSION) || (raise "[Pay] paddle gem must be version ~> 2.5")
24
23
  end
25
24
 
26
25
  def self.client
@@ -73,9 +72,12 @@ module Pay
73
72
  Pay::Webhooks.configure do |events|
74
73
  events.subscribe "paddle_classic.subscription_created", Pay::PaddleClassic::Webhooks::SubscriptionCreated.new
75
74
  events.subscribe "paddle_classic.subscription_updated", Pay::PaddleClassic::Webhooks::SubscriptionUpdated.new
76
- events.subscribe "paddle_classic.subscription_cancelled", Pay::PaddleClassic::Webhooks::SubscriptionCancelled.new
77
- events.subscribe "paddle_classic.subscription_payment_succeeded", Pay::PaddleClassic::Webhooks::SubscriptionPaymentSucceeded.new
78
- events.subscribe "paddle_classic.subscription_payment_refunded", Pay::PaddleClassic::Webhooks::SubscriptionPaymentRefunded.new
75
+ events.subscribe "paddle_classic.subscription_cancelled",
76
+ Pay::PaddleClassic::Webhooks::SubscriptionCancelled.new
77
+ events.subscribe "paddle_classic.subscription_payment_succeeded",
78
+ Pay::PaddleClassic::Webhooks::SubscriptionPaymentSucceeded.new
79
+ events.subscribe "paddle_classic.subscription_payment_refunded",
80
+ Pay::PaddleClassic::Webhooks::SubscriptionPaymentRefunded.new
79
81
  end
80
82
  end
81
83
  end
data/lib/pay/receipts.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  module Pay
2
2
  module Receipts
3
- def product
4
- Pay.application_name
5
- end
6
-
7
3
  def receipt_filename
8
4
  "receipt-#{created_at.strftime("%Y-%m-%d")}.pdf"
9
5
  end
@@ -21,6 +17,10 @@ module Pay
21
17
  ]
22
18
  end
23
19
 
20
+ def pdf_product_name
21
+ Pay.application_name
22
+ end
23
+
24
24
  def pdf_line_items
25
25
  items = [
26
26
  [
@@ -45,7 +45,7 @@ module Pay
45
45
  end
46
46
  end
47
47
  else
48
- items << [product, 1, Pay::Currency.format(amount, currency: currency), Pay::Currency.format(amount, currency: currency)]
48
+ items << [pdf_product_name, 1, Pay::Currency.format(amount, currency: currency), Pay::Currency.format(amount, currency: currency)]
49
49
  end
50
50
 
51
51
  # If no subtotal, we will display the total
@@ -155,7 +155,7 @@ module Pay
155
155
  company: {
156
156
  name: Pay.business_name,
157
157
  address: Pay.business_address,
158
- email: Pay.support_email.address,
158
+ email: Pay.support_email&.address,
159
159
  logo: Pay.business_logo
160
160
  },
161
161
  line_items: pdf_line_items
@@ -10,7 +10,7 @@ module Pay
10
10
 
11
11
  if (payment_intent_id = event.data.object.payment_intent)
12
12
  payment_intent = ::Stripe::PaymentIntent.retrieve({id: payment_intent_id}, {stripe_account: event.try(:account)}.compact)
13
- Pay::Stripe::Charge.sync(payment_intent.latest_charge, stripe_account: event.try(:account))
13
+ Pay::Stripe::Charge.sync(payment_intent.latest_charge, stripe_account: event.try(:account)) if payment_intent.latest_charge
14
14
  end
15
15
 
16
16
  if (subscription_id = event.data.object.subscription)
@@ -9,7 +9,7 @@ module Pay
9
9
  # Couldn't find user, we can skip
10
10
  return unless pay_customer.present?
11
11
 
12
- stripe_customer = pay_customer.customer
12
+ stripe_customer = pay_customer.api_record
13
13
 
14
14
  # Sync default card
15
15
  if (payment_method_id = stripe_customer.invoice_settings.default_payment_method)
@@ -8,7 +8,7 @@ module Pay
8
8
  pay_subscription = Pay::Subscription.find_by_processor_and_id(:stripe, object.id)
9
9
  return if pay_subscription.nil?
10
10
 
11
- pay_subscription.sync!
11
+ pay_subscription.sync!(stripe_account: event.try(:account))
12
12
 
13
13
  pay_user_mailer = Pay.mailer.with(pay_customer: pay_subscription.customer, pay_subscription: pay_subscription)
14
14
 
data/lib/pay/stripe.rb CHANGED
@@ -1,11 +1,8 @@
1
1
  module Pay
2
2
  module Stripe
3
- autoload :Billable, "pay/stripe/billable"
4
- autoload :Charge, "pay/stripe/charge"
5
- autoload :Error, "pay/stripe/error"
6
- autoload :Merchant, "pay/stripe/merchant"
7
- autoload :PaymentMethod, "pay/stripe/payment_method"
8
- autoload :Subscription, "pay/stripe/subscription"
3
+ class Error < Pay::Error
4
+ delegate :message, to: :cause
5
+ end
9
6
 
10
7
  module Webhooks
11
8
  autoload :AccountUpdated, "pay/stripe/webhooks/account_updated"
@@ -30,7 +27,7 @@ module Pay
30
27
 
31
28
  extend Env
32
29
 
33
- REQUIRED_VERSION = "~> 11"
30
+ REQUIRED_VERSION = "~> 12"
34
31
 
35
32
  # A list of database model names that include Pay
36
33
  # Used for safely looking up models with client_reference_id
@@ -66,6 +63,11 @@ module Pay
66
63
  find_value_by_name(:stripe, :signing_secret)
67
64
  end
68
65
 
66
+ def self.webhook_receive_test_events
67
+ value = find_value_by_name(:stripe, :webhook_receive_test_events)
68
+ value.blank? ? true : ActiveModel::Type::Boolean.new.cast(value)
69
+ end
70
+
69
71
  def self.configure_webhooks
70
72
  Pay::Webhooks.configure do |events|
71
73
  # Listen to the charge event to make sure we get non-subscription
@@ -138,5 +140,17 @@ module Pay
138
140
  Rails.logger.error "[Pay] Unable to locate record with: #{client_reference_id}"
139
141
  nil
140
142
  end
143
+
144
+ def self.sync_checkout_session(session_id, stripe_account: nil)
145
+ checkout_session = ::Stripe::Checkout::Session.retrieve({id: session_id, expand: ["payment_intent.latest_charge"]}, {stripe_account: stripe_account}.compact)
146
+ case checkout_session.mode
147
+ when "payment"
148
+ if (id = checkout_session.payment_intent.try(:latest_charge)&.id)
149
+ Pay::Stripe::Charge.sync(id, stripe_account: stripe_account)
150
+ end
151
+ when "subscription"
152
+ Pay::Stripe::Subscription.sync(checkout_session.subscription, stripe_account: stripe_account)
153
+ end
154
+ end
141
155
  end
142
156
  end
data/lib/pay/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pay
2
- VERSION = "7.2.1"
2
+ VERSION = "8.0.0"
3
3
  end
data/lib/pay.rb CHANGED
@@ -19,6 +19,7 @@ module Pay
19
19
  autoload :FakeProcessor, "pay/fake_processor"
20
20
  autoload :PaddleBilling, "pay/paddle_billing"
21
21
  autoload :PaddleClassic, "pay/paddle_classic"
22
+ autoload :LemonSqueezy, "pay/lemon_squeezy"
22
23
  autoload :Stripe, "pay/stripe"
23
24
 
24
25
  autoload :Webhooks, "pay/webhooks"
@@ -56,7 +57,7 @@ module Pay
56
57
  @@routes_path = "/pay"
57
58
 
58
59
  mattr_accessor :enabled_processors
59
- @@enabled_processors = [:stripe, :braintree, :paddle_billing, :paddle_classic]
60
+ @@enabled_processors = [:stripe, :braintree, :paddle_billing, :paddle_classic, :lemon_squeezy]
60
61
 
61
62
  mattr_accessor :send_emails
62
63
  @@send_emails = true
@@ -130,4 +131,14 @@ module Pay
130
131
  option
131
132
  end
132
133
  end
134
+
135
+ def self.sync(params)
136
+ if (session_id = params[:stripe_checkout_session_id] || params[:session_id])
137
+ Pay::Stripe.sync_checkout_session(session_id)
138
+ elsif (transaction_id = params[:paddle_billing_transaction_id] || params[:transaction_id])
139
+ Pay::PaddleBilling.sync_transaction(transaction_id)
140
+ elsif (order_id = params[:lemon_squeezy_order_id])
141
+ Pay::LemonSqueezy.sync_order(order_id)
142
+ end
143
+ end
133
144
  end