pay 2.4.4 → 2.5.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 +4 -4
- data/README.md +2 -3
- data/app/controllers/pay/webhooks/braintree_controller.rb +7 -53
- data/app/controllers/pay/webhooks/paddle_controller.rb +19 -18
- data/app/controllers/pay/webhooks/stripe_controller.rb +47 -0
- data/config/routes.rb +1 -1
- data/lib/pay.rb +12 -9
- data/lib/pay/braintree.rb +1 -0
- data/lib/pay/braintree/webhooks.rb +11 -0
- data/lib/pay/braintree/webhooks/subscription_canceled.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +24 -0
- data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +24 -0
- data/lib/pay/braintree/webhooks/subscription_expired.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_went_active.rb +19 -0
- data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +19 -0
- data/lib/pay/engine.rb +0 -1
- data/lib/pay/paddle/webhooks.rb +8 -0
- data/lib/pay/paddle/webhooks/subscription_cancelled.rb +3 -3
- data/lib/pay/paddle/webhooks/subscription_created.rb +15 -15
- data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
- data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +11 -11
- data/lib/pay/paddle/webhooks/subscription_updated.rb +11 -11
- data/lib/pay/stripe.rb +0 -1
- data/lib/pay/stripe/webhooks.rb +14 -15
- data/lib/pay/stripe/webhooks/subscription_created.rb +1 -0
- data/lib/pay/version.rb +1 -1
- data/lib/pay/webhooks/delegator.rb +61 -0
- metadata +12 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8aa4f779f21c561b6e8b48df4908424198b09413c0e2b580d7d6d2610e4d7429
|
4
|
+
data.tar.gz: c62e136c8ae6ffed79028922aedfe59f14e77ca235ae407aa5acaed3299918d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2154c6f3551b6089c470c71280c2ba9c810858954290fce62c67f2ca502822a4504447ebca822ac1b01f763c6c94095202693dc5b0d75e6dde4f89464530180e
|
7
|
+
data.tar.gz: 4d497228db1c45863ad0157bca179957f875705621b968eb1d4c0318c3e4f7ab35692260bb838a7f6c63aa39e704ac3398de535804d512461db1a8ac568239c8
|
data/README.md
CHANGED
@@ -9,8 +9,8 @@ Pay is a payments engine for Ruby on Rails 4.2 and higher.
|
|
9
9
|
**Current Payment Providers**
|
10
10
|
|
11
11
|
- Stripe ([supports SCA](https://stripe.com/docs/strong-customer-authentication) using API version `2020-08-27`)
|
12
|
-
- Braintree
|
13
|
-
- Paddle
|
12
|
+
- Braintree (supports PayPal)
|
13
|
+
- Paddle (supports PayPal)
|
14
14
|
|
15
15
|
Want to add a new payment provider? Contributions are welcome and the instructions [are here](https://github.com/jasoncharnes/pay/wiki/New-Payment-Provider).
|
16
16
|
|
@@ -31,7 +31,6 @@ gem 'pay', '~> 2.0'
|
|
31
31
|
|
32
32
|
# To use Stripe, also include:
|
33
33
|
gem 'stripe', '< 6.0', '>= 2.8'
|
34
|
-
gem 'stripe_event', '~> 2.3'
|
35
34
|
|
36
35
|
# To use Braintree + PayPal, also include:
|
37
36
|
gem 'braintree', '< 3.0', '>= 2.92.0'
|
@@ -6,66 +6,20 @@ module Pay
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def create
|
9
|
-
|
10
|
-
when "subscription_charged_successfully"
|
11
|
-
subscription_charged_successfully(webhook_notification)
|
12
|
-
when "subscription_canceled"
|
13
|
-
subscription_canceled(webhook_notification)
|
14
|
-
when "subscription_trial_ended"
|
15
|
-
subscription_trial_ended(webhook_notification)
|
16
|
-
end
|
17
|
-
|
18
|
-
render json: {success: true}, status: :ok
|
19
|
-
rescue ::Braintree::InvalidSignature
|
9
|
+
delegate_event(verified_event)
|
20
10
|
head :ok
|
11
|
+
rescue ::Braintree::InvalidSignature
|
12
|
+
head :bad_request
|
21
13
|
end
|
22
14
|
|
23
15
|
private
|
24
16
|
|
25
|
-
def
|
26
|
-
|
27
|
-
return if subscription.nil?
|
28
|
-
|
29
|
-
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
30
|
-
return unless pay_subscription.present?
|
31
|
-
|
32
|
-
billable = pay_subscription.owner
|
33
|
-
charge = billable.save_braintree_transaction(subscription.transactions.first)
|
34
|
-
|
35
|
-
if Pay.send_emails
|
36
|
-
Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def subscription_canceled(event)
|
41
|
-
subscription = event.subscription
|
42
|
-
return if subscription.nil?
|
43
|
-
|
44
|
-
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
45
|
-
return unless pay_subscription.present?
|
46
|
-
|
47
|
-
billable = pay_subscription.owner
|
48
|
-
return if billable.nil?
|
49
|
-
|
50
|
-
# User canceled or failed to make payments
|
51
|
-
billable.update(braintree_subscription_id: nil)
|
52
|
-
end
|
53
|
-
|
54
|
-
def subscription_trial_ended(event)
|
55
|
-
subscription = event.subscription
|
56
|
-
return if subscription.nil?
|
57
|
-
|
58
|
-
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
59
|
-
return unless pay_subscription.present?
|
60
|
-
|
61
|
-
pay_subscription.update(trial_ends_at: Time.zone.now)
|
17
|
+
def delegate_event(event)
|
18
|
+
Pay::Webhooks.instrument type: "braintree.#{event.kind}", event: event
|
62
19
|
end
|
63
20
|
|
64
|
-
def
|
65
|
-
|
66
|
-
params[:bt_signature],
|
67
|
-
params[:bt_payload]
|
68
|
-
)
|
21
|
+
def verified_event
|
22
|
+
Pay.braintree_gateway.webhook_notification.parse(params[:bt_signature], params[:bt_payload])
|
69
23
|
end
|
70
24
|
end
|
71
25
|
end
|
@@ -6,28 +6,29 @@ module Pay
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def create
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
Pay::Paddle::Webhooks::SubscriptionCreated.new(check_params.as_json)
|
14
|
-
when "subscription_updated"
|
15
|
-
Pay::Paddle::Webhooks::SubscriptionUpdated.new(check_params.as_json)
|
16
|
-
when "subscription_cancelled"
|
17
|
-
Pay::Paddle::Webhooks::SubscriptionCancelled.new(check_params.as_json)
|
18
|
-
when "subscription_payment_succeeded"
|
19
|
-
Pay::Paddle::Webhooks::SubscriptionPaymentSucceeded.new(check_params.as_json)
|
20
|
-
when "subscription_payment_refunded"
|
21
|
-
Pay::Paddle::Webhooks::SubscriptionPaymentRefunded.new(check_params.as_json)
|
22
|
-
end
|
23
|
-
render json: {success: true}, status: :ok
|
24
|
-
else
|
25
|
-
head :ok
|
26
|
-
end
|
9
|
+
delegate_event(verified_event)
|
10
|
+
head :ok
|
11
|
+
rescue Pay::Paddle::Error
|
12
|
+
head :bad_request
|
27
13
|
end
|
28
14
|
|
29
15
|
private
|
30
16
|
|
17
|
+
def delegate_event(event)
|
18
|
+
Pay::Webhooks.instrument type: "paddle.#{type}", event: event
|
19
|
+
end
|
20
|
+
|
21
|
+
def type
|
22
|
+
params[:alert_name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def verified_event
|
26
|
+
event = check_params.as_json
|
27
|
+
verifier = Pay::Paddle::Webhooks::SignatureVerifier.new(event)
|
28
|
+
return event if verifier.verify
|
29
|
+
raise Pay::Paddle::Error, "Unable to verify Paddle webhook event"
|
30
|
+
end
|
31
|
+
|
31
32
|
def check_params
|
32
33
|
params.except(:action, :controller).permit!
|
33
34
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Pay
|
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
|
+
|
8
|
+
def create
|
9
|
+
delegate_event(verified_event)
|
10
|
+
head :ok
|
11
|
+
rescue ::Stripe::SignatureVerificationError => e
|
12
|
+
log_error(e)
|
13
|
+
head :bad_request
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def delegate_event(event)
|
19
|
+
Pay::Webhooks.instrument type: "stripe.#{event.type}", event: event
|
20
|
+
end
|
21
|
+
|
22
|
+
def verified_event
|
23
|
+
payload = request.body.read
|
24
|
+
signature = request.headers["Stripe-Signature"]
|
25
|
+
possible_secrets = secrets(payload, signature)
|
26
|
+
|
27
|
+
possible_secrets.each_with_index do |secret, i|
|
28
|
+
return ::Stripe::Webhook.construct_event(payload, signature, secret.to_s)
|
29
|
+
rescue ::Stripe::SignatureVerificationError
|
30
|
+
raise if i == possible_secrets.length - 1
|
31
|
+
next
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def secrets(payload, signature)
|
36
|
+
secret = Pay::Stripe.signing_secret
|
37
|
+
return Array.wrap(secret) if secret
|
38
|
+
raise ::Stripe::SignatureVerificationError.new("Cannot verify signature without a Stripe signing secret", signature, http_body: payload)
|
39
|
+
end
|
40
|
+
|
41
|
+
def log_error(e)
|
42
|
+
logger.error e.message
|
43
|
+
e.backtrace.each { |line| logger.error " #{line}" }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/config/routes.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Pay::Engine.routes.draw do
|
4
4
|
resources :payments, only: [:show], module: :pay
|
5
|
-
post "webhooks/stripe", to: "
|
5
|
+
post "webhooks/stripe", to: "pay/webhooks/stripe#create"
|
6
6
|
post "webhooks/braintree", to: "pay/webhooks/braintree#create"
|
7
7
|
post "webhooks/paddle", to: "pay/webhooks/paddle#create"
|
8
8
|
end
|
data/lib/pay.rb
CHANGED
@@ -6,6 +6,18 @@ require "pay/payment"
|
|
6
6
|
require "pay/errors"
|
7
7
|
|
8
8
|
module Pay
|
9
|
+
module Webhooks
|
10
|
+
autoload :Delegator, "pay/webhooks/delegator"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
delegate :configure, :instrument, to: :delegator
|
14
|
+
|
15
|
+
def delegator
|
16
|
+
@delegator ||= Delegator.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
9
21
|
# Define who owns the subscription
|
10
22
|
mattr_accessor :billable_class
|
11
23
|
mattr_accessor :billable_table
|
@@ -37,15 +49,6 @@ module Pay
|
|
37
49
|
mattr_accessor :send_emails
|
38
50
|
@@send_emails = true
|
39
51
|
|
40
|
-
mattr_accessor :email_receipt_subject
|
41
|
-
@@email_receipt_subject = "Payment receipt"
|
42
|
-
mattr_accessor :email_refund_subject
|
43
|
-
@@email_refund_subject = "Payment refunded"
|
44
|
-
mattr_accessor :email_renewing_subject
|
45
|
-
@@email_renewing_subject = "Your upcoming subscription renewal"
|
46
|
-
mattr_accessor :email_action_required_subject
|
47
|
-
@@email_action_required_subject = "Confirm your payment"
|
48
|
-
|
49
52
|
mattr_accessor :automount_routes
|
50
53
|
@@automount_routes = true
|
51
54
|
|
data/lib/pay/braintree.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
Dir[File.join(__dir__, "webhooks", "**", "*.rb")].sort.each { |file| require file }
|
2
|
+
|
3
|
+
Pay::Webhooks.configure do |events|
|
4
|
+
events.subscribe "braintree.subscription_canceled", Pay::Braintree::Webhooks::SubscriptionCanceled.new
|
5
|
+
events.subscribe "braintree.subscription_charged_successfully", Pay::Braintree::Webhooks::SubscriptionChargedSuccessfully.new
|
6
|
+
events.subscribe "braintree.subscription_charged_unsuccessfully", Pay::Braintree::Webhooks::SubscriptionChargedUnsuccessfully.new
|
7
|
+
events.subscribe "braintree.subscription_expired", Pay::Braintree::Webhooks::SubscriptionExpired.new
|
8
|
+
events.subscribe "braintree.subscription_trial_ended", Pay::Braintree::Webhooks::SubscriptionTrialEnded.new
|
9
|
+
events.subscribe "braintree.subscription_went_active", Pay::Braintree::Webhooks::SubscriptionWentActive.new
|
10
|
+
events.subscribe "braintree.subscription_went_past_due", Pay::Braintree::Webhooks::SubscriptionWentPastDue.new
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# A subscription is canceled.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionCanceled
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
pay_subscription.update!(ends_at: Time.current, status: :canceled)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionChargedSuccessfully
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
billable = pay_subscription.owner
|
15
|
+
charge = billable.save_braintree_transaction(subscription.transactions.first)
|
16
|
+
|
17
|
+
if Pay.send_emails
|
18
|
+
Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# A subscription successfully moves to the next billing cycle. This will also occur when either a new transaction is created mid-cycle due to proration on an upgrade or a billing cycle is skipped due to the presence of a negative balance that covers the cost of the subscription.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionChargedUnsuccessfully
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
# billable = pay_subscription.owner
|
15
|
+
# charge = billable.save_braintree_transaction(subscription.transactions.first)
|
16
|
+
|
17
|
+
# if Pay.send_emails
|
18
|
+
# Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# A subscription reaches the specified number of billing cycles and expires.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionExpired
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
pay_subscription.update!(ends_at: Time.current, status: :canceled)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# A subscription's trial period ends.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionTrialEnded
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
pay_subscription.update(trial_ends_at: Time.zone.now)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# A subscription's first authorized transaction is created, or a successful transaction moves a subscription from the Past Due status to the Active status. Subscriptions with trial periods will not trigger this notification when they move from the trial period into the first billing cycle.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionWentActive
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
pay_subscription.update!(status: :active)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# A subscription has moved from the Active status to the Past Due status. This will only be triggered when the initial transaction in a billing cycle is declined. Once the status moves to past due, it will not be triggered again in that billing cycle.
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Braintree
|
5
|
+
module Webhooks
|
6
|
+
class SubscriptionWentPastDue
|
7
|
+
def call(event)
|
8
|
+
subscription = event.subscription
|
9
|
+
return if subscription.nil?
|
10
|
+
|
11
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
12
|
+
return unless pay_subscription.present?
|
13
|
+
|
14
|
+
pay_subscription.update!(status: :past_due)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/pay/engine.rb
CHANGED
data/lib/pay/paddle/webhooks.rb
CHANGED
@@ -1 +1,9 @@
|
|
1
1
|
Dir[File.join(__dir__, "webhooks", "**", "*.rb")].sort.each { |file| require file }
|
2
|
+
|
3
|
+
Pay::Webhooks.configure do |events|
|
4
|
+
events.subscribe "paddle.subscription_created", Pay::Paddle::Webhooks::SubscriptionCreated.new
|
5
|
+
events.subscribe "paddle.subscription_updated", Pay::Paddle::Webhooks::SubscriptionUpdated.new
|
6
|
+
events.subscribe "paddle.subscription_cancelled", Pay::Paddle::Webhooks::SubscriptionCancelled.new
|
7
|
+
events.subscribe "paddle.subscription_payment_succeeded", Pay::Paddle::Webhooks::SubscriptionPaymentSucceeded.new
|
8
|
+
events.subscribe "paddle.subscription_payment_refunded", Pay::Paddle::Webhooks::SubscriptionPaymentRefunded.new
|
9
|
+
end
|
@@ -2,15 +2,15 @@ module Pay
|
|
2
2
|
module Paddle
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionCancelled
|
5
|
-
def
|
6
|
-
subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id:
|
5
|
+
def call(event)
|
6
|
+
subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id: event["subscription_id"])
|
7
7
|
|
8
8
|
# We couldn't find the subscription for some reason, maybe it's from another service
|
9
9
|
return if subscription.nil?
|
10
10
|
|
11
11
|
# User canceled subscriptions have an ends_at
|
12
12
|
# Automatically canceled subscriptions need this value set
|
13
|
-
subscription.update!(ends_at: Time.zone.parse(
|
13
|
+
subscription.update!(ends_at: Time.zone.parse(event["cancellation_effective_date"])) if subscription.ends_at.blank? && event["cancellation_effective_date"].present?
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -2,42 +2,42 @@ module Pay
|
|
2
2
|
module Paddle
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionCreated
|
5
|
-
def
|
5
|
+
def call(event)
|
6
6
|
# We may already have the subscription in the database, so we can update that record
|
7
|
-
subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id:
|
7
|
+
subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id: event["subscription_id"])
|
8
8
|
|
9
9
|
# Create the subscription in the database if we don't have it already
|
10
10
|
if subscription.nil?
|
11
11
|
|
12
12
|
# The customer could already be in the database
|
13
|
-
owner = Pay.find_billable(processor: :paddle, processor_id:
|
13
|
+
owner = Pay.find_billable(processor: :paddle, processor_id: event["user_id"])
|
14
14
|
|
15
15
|
if owner.nil?
|
16
|
-
owner = owner_by_passtrough(
|
17
|
-
owner&.update!(processor: "paddle", processor_id:
|
16
|
+
owner = owner_by_passtrough(event["passthrough"], event["subscription_plan_id"])
|
17
|
+
owner&.update!(processor: "paddle", processor_id: event["user_id"])
|
18
18
|
end
|
19
19
|
|
20
20
|
if owner.nil?
|
21
|
-
Rails.logger.error("[Pay] Unable to find Pay::Billable with owner: '#{
|
21
|
+
Rails.logger.error("[Pay] Unable to find Pay::Billable with owner: '#{event["passthrough"]}'. Searched these models: #{Pay.billable_models.join(", ")}")
|
22
22
|
return
|
23
23
|
end
|
24
24
|
|
25
|
-
subscription = Pay.subscription_model.new(owner: owner, name: Pay.default_product_name, processor: "paddle", processor_id:
|
25
|
+
subscription = Pay.subscription_model.new(owner: owner, name: Pay.default_product_name, processor: "paddle", processor_id: event["subscription_id"], status: :active)
|
26
26
|
end
|
27
27
|
|
28
|
-
subscription.quantity =
|
29
|
-
subscription.processor_plan =
|
30
|
-
subscription.paddle_update_url =
|
31
|
-
subscription.paddle_cancel_url =
|
32
|
-
subscription.trial_ends_at = Time.zone.parse(
|
28
|
+
subscription.quantity = event["quantity"]
|
29
|
+
subscription.processor_plan = event["subscription_plan_id"]
|
30
|
+
subscription.paddle_update_url = event["update_url"]
|
31
|
+
subscription.paddle_cancel_url = event["cancel_url"]
|
32
|
+
subscription.trial_ends_at = Time.zone.parse(event["next_bill_date"]) if event["status"] == "trialing"
|
33
33
|
|
34
34
|
# If user was on trial, their subscription ends at the end of the trial
|
35
|
-
subscription.ends_at = if ["paused", "deleted"].include?(
|
35
|
+
subscription.ends_at = if ["paused", "deleted"].include?(event["status"]) && subscription.on_trial?
|
36
36
|
subscription.trial_ends_at
|
37
37
|
|
38
38
|
# User wasn't on trial, so subscription ends at period end
|
39
|
-
elsif ["paused", "deleted"].include?(
|
40
|
-
Time.zone.parse(
|
39
|
+
elsif ["paused", "deleted"].include?(event["status"])
|
40
|
+
Time.zone.parse(event["next_bill_date"])
|
41
41
|
|
42
42
|
# Subscription isn't marked to cancel at period end
|
43
43
|
end
|
@@ -2,11 +2,11 @@ module Pay
|
|
2
2
|
module Paddle
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionPaymentRefunded
|
5
|
-
def
|
6
|
-
charge = Pay.charge_model.find_by(processor: :paddle, processor_id:
|
5
|
+
def call(event)
|
6
|
+
charge = Pay.charge_model.find_by(processor: :paddle, processor_id: event["subscription_payment_id"])
|
7
7
|
return unless charge.present?
|
8
8
|
|
9
|
-
charge.update(amount_refunded: Integer(
|
9
|
+
charge.update(amount_refunded: Integer(event["gross_refund"].to_f * 100))
|
10
10
|
notify_user(charge.owner, charge)
|
11
11
|
end
|
12
12
|
|
@@ -2,29 +2,29 @@ module Pay
|
|
2
2
|
module Paddle
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionPaymentSucceeded
|
5
|
-
def
|
6
|
-
billable = Pay.find_billable(processor: :paddle, processor_id:
|
5
|
+
def call(event)
|
6
|
+
billable = Pay.find_billable(processor: :paddle, processor_id: event["user_id"])
|
7
7
|
return unless billable.present?
|
8
|
-
return if billable.charges.where(processor_id:
|
8
|
+
return if billable.charges.where(processor_id: event["subscription_payment_id"]).any?
|
9
9
|
|
10
|
-
charge = create_charge(billable,
|
10
|
+
charge = create_charge(billable, event)
|
11
11
|
notify_user(billable, charge)
|
12
12
|
end
|
13
13
|
|
14
|
-
def create_charge(user,
|
14
|
+
def create_charge(user, event)
|
15
15
|
charge = user.charges.find_or_initialize_by(
|
16
16
|
processor: :paddle,
|
17
|
-
processor_id:
|
17
|
+
processor_id: event["subscription_payment_id"]
|
18
18
|
)
|
19
19
|
|
20
20
|
params = {
|
21
|
-
amount: Integer(
|
22
|
-
card_type:
|
23
|
-
paddle_receipt_url:
|
24
|
-
created_at: Time.zone.parse(
|
21
|
+
amount: Integer(event["sale_gross"].to_f * 100),
|
22
|
+
card_type: event["payment_method"],
|
23
|
+
paddle_receipt_url: event["receipt_url"],
|
24
|
+
created_at: Time.zone.parse(event["event_time"])
|
25
25
|
}
|
26
26
|
|
27
|
-
payment_information = user.paddle_payment_information(
|
27
|
+
payment_information = user.paddle_payment_information(event["subscription_id"])
|
28
28
|
|
29
29
|
charge.update(params.merge(payment_information))
|
30
30
|
user.update(payment_information)
|
@@ -2,29 +2,29 @@ module Pay
|
|
2
2
|
module Paddle
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionUpdated
|
5
|
-
def
|
6
|
-
subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id:
|
5
|
+
def call(event)
|
6
|
+
subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id: event["subscription_id"])
|
7
7
|
|
8
8
|
return if subscription.nil?
|
9
9
|
|
10
|
-
case
|
10
|
+
case event["status"]
|
11
11
|
when "deleted"
|
12
12
|
subscription.status = "canceled"
|
13
|
-
subscription.ends_at = Time.zone.parse(
|
13
|
+
subscription.ends_at = Time.zone.parse(event["next_bill_date"]) || Time.zone.now if subscription.ends_at.blank?
|
14
14
|
when "trialing"
|
15
15
|
subscription.status = "trialing"
|
16
|
-
subscription.trial_ends_at = Time.zone.parse(
|
16
|
+
subscription.trial_ends_at = Time.zone.parse(event["next_bill_date"])
|
17
17
|
when "active"
|
18
18
|
subscription.status = "active"
|
19
|
-
subscription.paddle_paused_from = Time.zone.parse(
|
19
|
+
subscription.paddle_paused_from = Time.zone.parse(event["paused_from"]) if event["paused_from"].present?
|
20
20
|
else
|
21
|
-
subscription.status =
|
21
|
+
subscription.status = event["status"]
|
22
22
|
end
|
23
23
|
|
24
|
-
subscription.quantity =
|
25
|
-
subscription.processor_plan =
|
26
|
-
subscription.paddle_update_url =
|
27
|
-
subscription.paddle_cancel_url =
|
24
|
+
subscription.quantity = event["new_quantity"]
|
25
|
+
subscription.processor_plan = event["subscription_plan_id"]
|
26
|
+
subscription.paddle_update_url = event["update_url"]
|
27
|
+
subscription.paddle_cancel_url = event["cancel_url"]
|
28
28
|
|
29
29
|
# If user was on trial, their subscription ends at the end of the trial
|
30
30
|
subscription.ends_at = subscription.trial_ends_at if subscription.on_trial?
|
data/lib/pay/stripe.rb
CHANGED
@@ -13,7 +13,6 @@ module Pay
|
|
13
13
|
def setup
|
14
14
|
::Stripe.api_key = private_key
|
15
15
|
::Stripe.api_version = "2020-08-27"
|
16
|
-
::StripeEvent.signing_secret = signing_secret
|
17
16
|
|
18
17
|
# Used by Stripe to identify Pay for support
|
19
18
|
::Stripe.set_app_info("PayRails", partner_id: "pp_partner_IqhY0UExnJYLxg", version: Pay::VERSION, url: "https://github.com/pay-rails/pay")
|
data/lib/pay/stripe/webhooks.rb
CHANGED
@@ -1,39 +1,38 @@
|
|
1
|
-
require "stripe_event"
|
2
1
|
Dir[File.join(__dir__, "webhooks", "**", "*.rb")].sort.each { |file| require file }
|
3
2
|
|
4
|
-
|
3
|
+
Pay::Webhooks.configure do |events|
|
5
4
|
# Listen to the charge event to make sure we get non-subscription
|
6
5
|
# purchases as well. Invoice is only for subscriptions and manual creation
|
7
6
|
# so it does not include individual charges.
|
8
|
-
events.subscribe "charge.succeeded", Pay::Stripe::Webhooks::ChargeSucceeded.new
|
9
|
-
events.subscribe "charge.refunded", Pay::Stripe::Webhooks::ChargeRefunded.new
|
7
|
+
events.subscribe "stripe.charge.succeeded", Pay::Stripe::Webhooks::ChargeSucceeded.new
|
8
|
+
events.subscribe "stripe.charge.refunded", Pay::Stripe::Webhooks::ChargeRefunded.new
|
10
9
|
|
11
10
|
# Warn user of upcoming charges for their subscription. This is handy for
|
12
11
|
# notifying annual users their subscription will renew shortly.
|
13
12
|
# This probably should be ignored for monthly subscriptions.
|
14
|
-
events.subscribe "invoice.upcoming", Pay::Stripe::Webhooks::SubscriptionRenewing.new
|
13
|
+
events.subscribe "stripe.invoice.upcoming", Pay::Stripe::Webhooks::SubscriptionRenewing.new
|
15
14
|
|
16
15
|
# Payment action is required to process an invoice
|
17
|
-
events.subscribe "invoice.payment_action_required", Pay::Stripe::Webhooks::PaymentActionRequired.new
|
16
|
+
events.subscribe "stripe.invoice.payment_action_required", Pay::Stripe::Webhooks::PaymentActionRequired.new
|
18
17
|
|
19
18
|
# If a subscription is manually created on Stripe, we want to sync
|
20
|
-
events.subscribe "customer.subscription.created", Pay::Stripe::Webhooks::SubscriptionCreated.new
|
19
|
+
events.subscribe "stripe.customer.subscription.created", Pay::Stripe::Webhooks::SubscriptionCreated.new
|
21
20
|
|
22
21
|
# If the plan, quantity, or trial ending date is updated on Stripe, we want to sync
|
23
|
-
events.subscribe "customer.subscription.updated", Pay::Stripe::Webhooks::SubscriptionUpdated.new
|
22
|
+
events.subscribe "stripe.customer.subscription.updated", Pay::Stripe::Webhooks::SubscriptionUpdated.new
|
24
23
|
|
25
24
|
# When a customers subscription is canceled, we want to update our records
|
26
|
-
events.subscribe "customer.subscription.deleted", Pay::Stripe::Webhooks::SubscriptionDeleted.new
|
25
|
+
events.subscribe "stripe.customer.subscription.deleted", Pay::Stripe::Webhooks::SubscriptionDeleted.new
|
27
26
|
|
28
27
|
# Monitor changes for customer's default card changing
|
29
|
-
events.subscribe "customer.updated", Pay::Stripe::Webhooks::CustomerUpdated.new
|
28
|
+
events.subscribe "stripe.customer.updated", Pay::Stripe::Webhooks::CustomerUpdated.new
|
30
29
|
|
31
30
|
# If a customer was deleted in Stripe, their subscriptions should be cancelled
|
32
|
-
events.subscribe "customer.deleted", Pay::Stripe::Webhooks::CustomerDeleted.new
|
31
|
+
events.subscribe "stripe.customer.deleted", Pay::Stripe::Webhooks::CustomerDeleted.new
|
33
32
|
|
34
33
|
# If a customer's payment source was deleted in Stripe, we should update as well
|
35
|
-
events.subscribe "payment_method.attached", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
36
|
-
events.subscribe "payment_method.updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
37
|
-
events.subscribe "payment_method.card_automatically_updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
38
|
-
events.subscribe "payment_method.detached", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
34
|
+
events.subscribe "stripe.payment_method.attached", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
35
|
+
events.subscribe "stripe.payment_method.updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
36
|
+
events.subscribe "stripe.payment_method.card_automatically_updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
37
|
+
events.subscribe "stripe.payment_method.detached", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
|
39
38
|
end
|
data/lib/pay/version.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Pay
|
2
|
+
module Webhooks
|
3
|
+
class Delegator
|
4
|
+
attr_reader :backend
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@backend = ActiveSupport::Notifications
|
8
|
+
end
|
9
|
+
|
10
|
+
# Configure DSL
|
11
|
+
def configure(&block)
|
12
|
+
raise ArgumentError, "must provide a block" unless block
|
13
|
+
block.arity.zero? ? instance_eval(&block) : yield(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Subscribe to specific events
|
17
|
+
def subscribe(name, callable = nil, &block)
|
18
|
+
callable ||= block
|
19
|
+
backend.subscribe to_regexp(name), NotificationAdapter.new(callable)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Listen to all events
|
23
|
+
def all(callable = nil, &block)
|
24
|
+
callable ||= block
|
25
|
+
subscribe nil, callable
|
26
|
+
end
|
27
|
+
|
28
|
+
# Unsubscribe
|
29
|
+
def unsubscribe(name)
|
30
|
+
backend.unsubscribe name
|
31
|
+
end
|
32
|
+
|
33
|
+
# Called to process an event
|
34
|
+
def instrument(event:, type:)
|
35
|
+
backend.instrument name_with_namespace(type), event
|
36
|
+
end
|
37
|
+
|
38
|
+
# Strips down to event data only
|
39
|
+
class NotificationAdapter
|
40
|
+
def initialize(subscriber)
|
41
|
+
@subscriber = subscriber
|
42
|
+
end
|
43
|
+
|
44
|
+
def call(*args)
|
45
|
+
payload = args.last
|
46
|
+
@subscriber.call(payload)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def to_regexp(name)
|
53
|
+
%r{^#{Regexp.escape name_with_namespace(name)}}
|
54
|
+
end
|
55
|
+
|
56
|
+
def name_with_namespace(name, delimiter: ".")
|
57
|
+
[:pay, name].join(delimiter)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
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: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Charnes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-02-
|
12
|
+
date: 2021-02-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -59,20 +59,6 @@ dependencies:
|
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '2.8'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: stripe_event
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '2.3'
|
69
|
-
type: :development
|
70
|
-
prerelease: false
|
71
|
-
version_requirements: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '2.3'
|
76
62
|
- !ruby/object:Gem::Dependency
|
77
63
|
name: paddle_pay
|
78
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -181,6 +167,7 @@ files:
|
|
181
167
|
- app/controllers/pay/payments_controller.rb
|
182
168
|
- app/controllers/pay/webhooks/braintree_controller.rb
|
183
169
|
- app/controllers/pay/webhooks/paddle_controller.rb
|
170
|
+
- app/controllers/pay/webhooks/stripe_controller.rb
|
184
171
|
- app/helpers/pay/application_helper.rb
|
185
172
|
- app/jobs/pay/application_job.rb
|
186
173
|
- app/jobs/pay/email_sync_job.rb
|
@@ -214,6 +201,14 @@ files:
|
|
214
201
|
- lib/pay/braintree/billable.rb
|
215
202
|
- lib/pay/braintree/charge.rb
|
216
203
|
- lib/pay/braintree/subscription.rb
|
204
|
+
- lib/pay/braintree/webhooks.rb
|
205
|
+
- lib/pay/braintree/webhooks/subscription_canceled.rb
|
206
|
+
- lib/pay/braintree/webhooks/subscription_charged_successfully.rb
|
207
|
+
- lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb
|
208
|
+
- lib/pay/braintree/webhooks/subscription_expired.rb
|
209
|
+
- lib/pay/braintree/webhooks/subscription_trial_ended.rb
|
210
|
+
- lib/pay/braintree/webhooks/subscription_went_active.rb
|
211
|
+
- lib/pay/braintree/webhooks/subscription_went_past_due.rb
|
217
212
|
- lib/pay/engine.rb
|
218
213
|
- lib/pay/env.rb
|
219
214
|
- lib/pay/errors.rb
|
@@ -246,6 +241,7 @@ files:
|
|
246
241
|
- lib/pay/stripe/webhooks/subscription_renewing.rb
|
247
242
|
- lib/pay/stripe/webhooks/subscription_updated.rb
|
248
243
|
- lib/pay/version.rb
|
244
|
+
- lib/pay/webhooks/delegator.rb
|
249
245
|
homepage: https://github.com/jasoncharnes/pay
|
250
246
|
licenses:
|
251
247
|
- MIT
|