pay 7.2.0 → 7.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b50cc63cfa61beffbc725d9f6c623c469a6357e0808ff06d3e814c4aa4e19955
4
- data.tar.gz: ae87dbfcd24862baa2f156cb2b1b94fe836a0d113853b562cbe59835e59569c5
3
+ metadata.gz: 3ec013c6c05d0e8027b628258882c9bac3a01f7942e9654cc52582643cf57629
4
+ data.tar.gz: 75bcbbfcd8b63bb0f73e97b4a4da41335923f5f3af2e8577fc1f2aa94baa8542
5
5
  SHA512:
6
- metadata.gz: 459524ff5b772a08c1ee1d994088b71f0e9a854f4b1afa76ecdb60fa8229f43ea72ed327a6dbf55d497478d033f99f1d19c4ed978c69931e27ad9ddc99fd57af
7
- data.tar.gz: 200898e080a94d76520e0a85088d43691f64a27da3d94ac4fa74a9f14db4f6a5c1bdc16992422b3f4c4ab9b63bb35344e099799984cca0beb4cff8a6dc6e8512
6
+ metadata.gz: dd2822689a009a126f016365182312e4555098ba40b429e16c1dc41b76902a4fd18da06e3465b25cf17e0bf53371adf147bfd3c72e2c9186ce965de304f403e4
7
+ data.tar.gz: 1ad32b2e21be33e58ffb855dec4275cdff66d036a1728f2573d54d61a2937c6aa6c4602cb59f1dc53bea2edcd4fac46f89efcb91a5ea22443669087569959e5b
@@ -6,7 +6,8 @@ module Pay
6
6
  end
7
7
 
8
8
  def create
9
- queue_event(verified_event)
9
+ event = verified_event
10
+ queue_event(event) if event.livemode || Pay::Stripe.webhook_receive_test_events
10
11
  head :ok
11
12
  rescue ::Stripe::SignatureVerificationError => e
12
13
  log_error(e)
@@ -79,7 +79,11 @@ module Pay
79
79
  }
80
80
  )
81
81
 
82
- pay_customer.reload_default_payment_method if default
82
+ if default
83
+ pay_customer.payment_methods.where.not(id: pay_payment_method.id).update_all(default: false)
84
+ pay_customer.reload_default_payment_method
85
+ end
86
+
83
87
  pay_payment_method
84
88
  end
85
89
 
@@ -0,0 +1,90 @@
1
+ module Pay
2
+ module PaddleBilling
3
+ class Billable
4
+ attr_reader :pay_customer
5
+
6
+ delegate :processor_id,
7
+ :processor_id?,
8
+ :email,
9
+ :customer_name,
10
+ :card_token,
11
+ to: :pay_customer
12
+
13
+ def initialize(pay_customer)
14
+ @pay_customer = pay_customer
15
+ end
16
+
17
+ def customer_attributes
18
+ {email: email, name: customer_name}
19
+ end
20
+
21
+ # Retrieves a Paddle::Customer object
22
+ #
23
+ # Finds an existing Paddle::Customer if processor_id exists
24
+ # Creates a new Paddle::Customer using `email` and `customer_name` if empty processor_id
25
+ #
26
+ # Returns a Paddle::Customer object
27
+ def customer
28
+ if processor_id?
29
+ ::Paddle::Customer.retrieve(id: processor_id)
30
+ else
31
+ sc = ::Paddle::Customer.create(email: email, name: customer_name)
32
+ pay_customer.update!(processor_id: sc.id)
33
+ sc
34
+ end
35
+ rescue ::Paddle::Error => e
36
+ raise Pay::PaddleBilling::Error, e
37
+ end
38
+
39
+ # Syncs name and email to Paddle::Customer
40
+ # You can also pass in other attributes that will be merged into the default attributes
41
+ def update_customer!(**attributes)
42
+ customer unless processor_id?
43
+ attrs = customer_attributes.merge(attributes)
44
+ ::Paddle::Customer.update(id: processor_id, **attrs)
45
+ end
46
+
47
+ def charge(amount, options = {})
48
+ return Pay::Error unless options
49
+
50
+ items = options[:items]
51
+ opts = options.except(:items).merge(customer_id: processor_id)
52
+ transaction = ::Paddle::Transaction.create(items: items, **opts)
53
+
54
+ attrs = {
55
+ amount: transaction.details.totals.grand_total,
56
+ created_at: transaction.created_at,
57
+ currency: transaction.currency_code,
58
+ metadata: transaction.details.line_items&.first&.id
59
+ }
60
+
61
+ charge = pay_customer.charges.find_or_initialize_by(processor_id: transaction.id)
62
+ charge.update(attrs)
63
+ charge
64
+ rescue ::Paddle::Error => e
65
+ raise Pay::PaddleBilling::Error, e
66
+ end
67
+
68
+ def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
69
+ # pass
70
+ end
71
+
72
+ # Paddle does not use payment method tokens. The method signature has it here
73
+ # to have a uniform API with the other payment processors.
74
+ def add_payment_method(token = nil, default: true)
75
+ Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer)
76
+ end
77
+
78
+ def trial_end_date(subscription)
79
+ return unless subscription.state == "trialing"
80
+ Time.zone.parse(subscription.next_payment[:date]).end_of_day
81
+ end
82
+
83
+ def processor_subscription(subscription_id, options = {})
84
+ ::Paddle::Subscription.retrieve(id: subscription_id, **options)
85
+ rescue ::Paddle::Error => e
86
+ raise Pay::PaddleBilling::Error, e
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,68 @@
1
+ module Pay
2
+ module PaddleBilling
3
+ class Charge
4
+ attr_reader :pay_charge
5
+
6
+ delegate :processor_id, :customer, to: :pay_charge
7
+
8
+ def initialize(pay_charge)
9
+ @pay_charge = pay_charge
10
+ end
11
+
12
+ def self.sync(charge_id, object: nil, try: 0, retries: 1)
13
+ # Skip loading the latest charge details from the API if we already have it
14
+ object ||= ::Paddle::Transaction.retrieve(id: charge_id)
15
+
16
+ # Ignore transactions that aren't completed
17
+ return unless object.status == "completed"
18
+
19
+ # Ignore charges without a Customer
20
+ return if object.customer_id.blank?
21
+
22
+ pay_customer = Pay::Customer.find_by(processor: :paddle_billing, processor_id: object.customer_id)
23
+ return unless pay_customer
24
+
25
+ # Ignore transactions that are payment method changes
26
+ # But update the customer's payment method
27
+ if object.origin == "subscription_payment_method_change"
28
+ Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer, attributes: object.payments.first)
29
+ return
30
+ end
31
+
32
+ attrs = {
33
+ amount: object.details.totals.grand_total,
34
+ created_at: object.created_at,
35
+ currency: object.currency_code,
36
+ metadata: object.details.line_items&.first&.id,
37
+ subscription: pay_customer.subscriptions.find_by(processor_id: object.subscription_id)
38
+ }
39
+
40
+ if object.payment
41
+ case object.payment.method_details.type.downcase
42
+ when "card"
43
+ attrs[:payment_method_type] = "card"
44
+ attrs[:brand] = details.card.type
45
+ attrs[:exp_month] = details.card.expiry_month
46
+ attrs[:exp_year] = details.card.expiry_year
47
+ attrs[:last4] = details.card.last4
48
+ when "paypal"
49
+ attrs[:payment_method_type] = "paypal"
50
+ end
51
+
52
+ # Update customer's payment method
53
+ Pay::PaddleBilling::PaymentMethod.sync(pay_customer: pay_customer, attributes: object.payments.first)
54
+ end
55
+
56
+ # Update or create the charge
57
+ if (pay_charge = pay_customer.charges.find_by(processor_id: object.id))
58
+ pay_charge.with_lock do
59
+ pay_charge.update!(attrs)
60
+ end
61
+ pay_charge
62
+ else
63
+ pay_customer.charges.create!(attrs.merge(processor_id: object.id))
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,7 @@
1
+ module Pay
2
+ module PaddleBilling
3
+ class Error < Pay::Error
4
+ delegate :message, to: :cause
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module Pay
2
+ module PaddleBilling
3
+ class PaymentMethod
4
+ attr_reader :pay_payment_method
5
+
6
+ delegate :customer, :processor_id, to: :pay_payment_method
7
+
8
+ def self.sync(pay_customer:, attributes:)
9
+ details = attributes.method_details
10
+ attrs = {
11
+ type: details.type.downcase
12
+ }
13
+
14
+ case details.type.downcase
15
+ when "card"
16
+ attrs[:brand] = details.card.type
17
+ attrs[:last4] = details.card.last4
18
+ attrs[:exp_month] = details.card.expiry_month
19
+ attrs[:exp_year] = details.card.expiry_year
20
+ end
21
+
22
+ payment_method = pay_customer.payment_methods.find_or_initialize_by(processor_id: attributes.stored_payment_method_id)
23
+ payment_method.update!(attrs)
24
+ payment_method
25
+ end
26
+
27
+ def initialize(pay_payment_method)
28
+ @pay_payment_method = pay_payment_method
29
+ end
30
+
31
+ # Sets payment method as default
32
+ def make_default!
33
+ end
34
+
35
+ # Remove payment method
36
+ def detach
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,185 @@
1
+ module Pay
2
+ module PaddleBilling
3
+ class Subscription
4
+ attr_reader :pay_subscription
5
+
6
+ delegate :active?,
7
+ :canceled?,
8
+ :on_grace_period?,
9
+ :on_trial?,
10
+ :ends_at,
11
+ :name,
12
+ :owner,
13
+ :pause_starts_at,
14
+ :pause_starts_at?,
15
+ :processor_id,
16
+ :processor_plan,
17
+ :processor_subscription,
18
+ :prorate,
19
+ :prorate?,
20
+ :quantity,
21
+ :quantity?,
22
+ :trial_ends_at,
23
+ to: :pay_subscription
24
+
25
+ def self.sync_from_transaction(transaction_id)
26
+ transaction = ::Paddle::Transaction.retrieve(id: transaction_id)
27
+ sync(transaction.subscription_id) if transaction.subscription_id
28
+ end
29
+
30
+ def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
31
+ # Passthrough is not return from this API, so we can't use that
32
+ object ||= ::Paddle::Subscription.retrieve(id: subscription_id)
33
+
34
+ pay_customer = Pay::Customer.find_by(processor: :paddle_billing, processor_id: object.customer_id)
35
+ return unless pay_customer
36
+
37
+ attributes = {
38
+ current_period_end: object.current_billing_period&.ends_at,
39
+ current_period_start: object.current_billing_period&.starts_at,
40
+ ends_at: (object.canceled_at ? Time.parse(object.canceled_at) : nil),
41
+ metadata: object.custom_data,
42
+ paddle_cancel_url: object.management_urls&.cancel,
43
+ paddle_update_url: object.management_urls&.update_payment_method,
44
+ pause_starts_at: (object.paused_at ? Time.parse(object.paused_at) : nil),
45
+ status: object.status
46
+ }
47
+
48
+ if object.items&.first
49
+ item = object.items.first
50
+ attributes[:processor_plan] = item.price.id
51
+ attributes[:quantity] = item.quantity
52
+ end
53
+
54
+ case attributes[:status]
55
+ when "canceled"
56
+ # Remove payment methods since customer cannot be reused after cancelling
57
+ Pay::PaymentMethod.where(customer_id: object.customer_id).destroy_all
58
+ when "trialing"
59
+ attributes[:trial_ends_at] = Time.parse(object.next_billed_at)
60
+ when "paused"
61
+ attributes[:pause_starts_at] = Time.parse(object.paused_at)
62
+ end
63
+
64
+ case object.scheduled_change&.action
65
+ when "cancel"
66
+ attributes[:ends_at] = Time.parse(object.scheduled_change.effective_at)
67
+ when "pause"
68
+ attributes[:pause_starts_at] = Time.parse(object.scheduled_change.effective_at)
69
+ when "resume"
70
+ attributes[:pause_resumes_at] = Time.parse(object.scheduled_change.effective_at)
71
+ end
72
+
73
+ # Update or create the subscription
74
+ if (pay_subscription = pay_customer.subscriptions.find_by(processor_id: subscription_id))
75
+ pay_subscription.with_lock do
76
+ pay_subscription.update!(attributes)
77
+ end
78
+ pay_subscription
79
+ else
80
+ pay_customer.subscriptions.create!(attributes.merge(name: name, processor_id: subscription_id))
81
+ end
82
+ end
83
+
84
+ def initialize(pay_subscription)
85
+ @pay_subscription = pay_subscription
86
+ end
87
+
88
+ def subscription(**options)
89
+ @paddle_billing_subscription ||= ::Paddle::Subscription.retrieve(id: processor_id, **options)
90
+ end
91
+
92
+ # Get a transaction to update payment method
93
+ def payment_method_transaction
94
+ ::Paddle::Subscription.get_transaction(id: processor_id)
95
+ end
96
+
97
+ # If a subscription is paused, cancel immediately
98
+ # Otherwise, cancel at period end
99
+ def cancel(**options)
100
+ return if canceled?
101
+
102
+ response = ::Paddle::Subscription.cancel(
103
+ id: processor_id,
104
+ effective_from: options.fetch(:effective_from, (paused? ? "immediately" : "next_billing_period"))
105
+ )
106
+ pay_subscription.update(
107
+ status: response.status,
108
+ ends_at: response.scheduled_change.effective_at
109
+ )
110
+ rescue ::Paddle::Error => e
111
+ raise Pay::PaddleBilling::Error, e
112
+ end
113
+
114
+ def cancel_now!(**options)
115
+ cancel(options.merge(effective_from: "immediately"))
116
+ rescue ::Paddle::Error => e
117
+ raise Pay::PaddleBilling::Error, e
118
+ end
119
+
120
+ def change_quantity(quantity, **options)
121
+ items = [{
122
+ price_id: processor_plan,
123
+ quantity: quantity
124
+ }]
125
+
126
+ ::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
127
+ rescue ::Paddle::Error => e
128
+ raise Pay::PaddleBilling::Error, e
129
+ end
130
+
131
+ # A subscription could be set to cancel or pause in the future
132
+ # It is considered on grace period until the cancel or pause time begins
133
+ def on_grace_period?
134
+ (canceled? && Time.current < ends_at) || (paused? && pause_starts_at? && Time.current < pause_starts_at)
135
+ end
136
+
137
+ def paused?
138
+ pay_subscription.status == "paused"
139
+ end
140
+
141
+ def pause
142
+ response = ::Paddle::Subscription.pause(id: processor_id)
143
+ pay_subscription.update!(status: :paused, pause_starts_at: response.scheduled_change.effective_at)
144
+ rescue ::Paddle::Error => e
145
+ raise Pay::PaddleBilling::Error, e
146
+ end
147
+
148
+ def resumable?
149
+ paused?
150
+ end
151
+
152
+ def resume
153
+ unless resumable?
154
+ raise StandardError, "You can only resume paused subscriptions."
155
+ end
156
+
157
+ # Paddle Billing API only allows "resuming" subscriptions when they are paused
158
+ # So cancel the scheduled change if it is in the future
159
+ if paused? && pause_starts_at? && Time.current < pause_starts_at
160
+ ::Paddle::Subscription.update(id: processor_id, scheduled_change: nil)
161
+ else
162
+ ::Paddle::Subscription.resume(id: processor_id, effective_from: "immediately")
163
+ end
164
+
165
+ pay_subscription.update(status: :active, pause_starts_at: nil)
166
+ rescue ::Paddle::Error => e
167
+ raise Pay::PaddleBilling::Error, e
168
+ end
169
+
170
+ def swap(plan, **options)
171
+ items = [{
172
+ price_id: plan,
173
+ quantity: quantity || 1
174
+ }]
175
+
176
+ ::Paddle::Subscription.update(id: processor_id, items: items, proration_billing_mode: "prorated_immediately")
177
+ pay_subscription.update(processor_plan: plan, ends_at: nil, status: :active)
178
+ end
179
+
180
+ # Retries the latest invoice for a Past Due subscription
181
+ def retry_failed_payment
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,11 @@
1
+ module Pay
2
+ module PaddleBilling
3
+ module Webhooks
4
+ class Subscription
5
+ def call(event)
6
+ Pay::PaddleBilling::Subscription.sync(event.id, object: event)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Pay
2
+ module PaddleBilling
3
+ module Webhooks
4
+ class TransactionCompleted
5
+ def call(event)
6
+ Pay::PaddleBilling::Charge.sync(event.id)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,138 @@
1
+ module Pay
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"
9
+
10
+ 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"
29
+ end
30
+
31
+ extend Env
32
+
33
+ REQUIRED_VERSION = "~> 1"
34
+
35
+ def self.enabled?
36
+ return false unless Pay.enabled_processors.include?(:lemonsqueezy) && defined?(::Lemonzsqueezy)
37
+
38
+ Pay::Engine.version_matches?(required: REQUIRED_VERSION, current: ::Lemonsqueezy::VERSION) || (raise "[Pay] lemonsqueezy gem must be version #{REQUIRED_VERSION}")
39
+ end
40
+
41
+ def self.setup
42
+ ::Stripe.api_key = private_key
43
+
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")
46
+
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
51
+ end
52
+
53
+ def self.public_key
54
+ find_value_by_name(:stripe, :public_key)
55
+ end
56
+
57
+ def self.private_key
58
+ find_value_by_name(:stripe, :private_key)
59
+ end
60
+
61
+ def self.signing_secret
62
+ find_value_by_name(:stripe, :signing_secret)
63
+ end
64
+
65
+ def self.configure_webhooks
66
+ 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
116
+ end
117
+ end
118
+
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("_")
122
+ end
123
+
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)
131
+
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
136
+ end
137
+ end
138
+ end
@@ -261,6 +261,8 @@ module Pay
261
261
  #
262
262
  # pause_behavior of `void` is considered active until the end of the current period and not active after that. The current_period_end is stored as `pause_starts_at`
263
263
  # Other pause_behaviors do not set `pause_starts_at` because they are used for offering free services
264
+ #
265
+ # https://docs.stripe.com/billing/subscriptions/pause-payment
264
266
  def pause(**options)
265
267
  attributes = {pause_collection: options.reverse_merge(behavior: "void")}
266
268
  @stripe_subscription = ::Stripe::Subscription.update(processor_id, attributes.merge(expand_options), stripe_options)
@@ -273,8 +275,10 @@ module Pay
273
275
  end
274
276
 
275
277
  # Unpauses a subscription
278
+ #
279
+ # https://docs.stripe.com/billing/subscriptions/pause-payment#unpausing
276
280
  def unpause
277
- @stripe_subscription = ::Stripe::Subscription.update(processor_id, {pause_collection: nil}.merge(expand_options), stripe_options)
281
+ @stripe_subscription = ::Stripe::Subscription.update(processor_id, {pause_collection: ""}.merge(expand_options), stripe_options)
278
282
  pay_subscription.update(
279
283
  pause_behavior: nil,
280
284
  pause_resumes_at: nil,
@@ -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)
@@ -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
@@ -30,7 +30,7 @@ module Pay
30
30
 
31
31
  extend Env
32
32
 
33
- REQUIRED_VERSION = "~> 11"
33
+ REQUIRED_VERSION = "~> 12"
34
34
 
35
35
  # A list of database model names that include Pay
36
36
  # Used for safely looking up models with client_reference_id
@@ -66,6 +66,11 @@ module Pay
66
66
  find_value_by_name(:stripe, :signing_secret)
67
67
  end
68
68
 
69
+ def self.webhook_receive_test_events
70
+ value = find_value_by_name(:stripe, :webhook_receive_test_events)
71
+ value.blank? ? true : ActiveModel::Type::Boolean.new.cast(value)
72
+ end
73
+
69
74
  def self.configure_webhooks
70
75
  Pay::Webhooks.configure do |events|
71
76
  # Listen to the charge event to make sure we get non-subscription
data/lib/pay/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pay
2
- VERSION = "7.2.0"
2
+ VERSION = "7.3.0"
3
3
  end
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: 7.2.0
4
+ version: 7.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Charnes
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-05-16 00:00:00.000000000 Z
13
+ date: 2024-07-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -104,6 +104,14 @@ files:
104
104
  - lib/pay/fake_processor/merchant.rb
105
105
  - lib/pay/fake_processor/payment_method.rb
106
106
  - lib/pay/fake_processor/subscription.rb
107
+ - lib/pay/lemon_squeezy.rb
108
+ - lib/pay/lemon_squeezy/billable.rb
109
+ - lib/pay/lemon_squeezy/charge.rb
110
+ - lib/pay/lemon_squeezy/error.rb
111
+ - lib/pay/lemon_squeezy/payment_method.rb
112
+ - lib/pay/lemon_squeezy/subscription.rb
113
+ - lib/pay/lemon_squeezy/webhooks/subscription.rb
114
+ - lib/pay/lemon_squeezy/webhooks/transaction_completed.rb
107
115
  - lib/pay/nano_id.rb
108
116
  - lib/pay/paddle_billing.rb
109
117
  - lib/pay/paddle_billing/billable.rb
@@ -176,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
184
  - !ruby/object:Gem::Version
177
185
  version: '0'
178
186
  requirements: []
179
- rubygems_version: 3.5.6
187
+ rubygems_version: 3.5.13
180
188
  signing_key:
181
189
  specification_version: 4
182
190
  summary: Payments engine for Ruby on Rails