pay 2.2.2 → 2.3.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.

Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +137 -7
  3. data/Rakefile +2 -4
  4. data/app/controllers/pay/payments_controller.rb +2 -0
  5. data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
  6. data/app/models/pay/application_record.rb +6 -1
  7. data/app/models/pay/charge.rb +7 -0
  8. data/app/models/pay/subscription.rb +24 -3
  9. data/config/routes.rb +1 -0
  10. data/db/migrate/20200603134434_add_data_to_pay_models.rb +17 -0
  11. data/lib/pay.rb +3 -0
  12. data/lib/pay/billable.rb +5 -0
  13. data/lib/pay/braintree/billable.rb +10 -4
  14. data/lib/pay/braintree/charge.rb +4 -0
  15. data/lib/pay/braintree/subscription.rb +6 -0
  16. data/lib/pay/engine.rb +7 -0
  17. data/lib/pay/paddle.rb +38 -0
  18. data/lib/pay/paddle/billable.rb +66 -0
  19. data/lib/pay/paddle/charge.rb +39 -0
  20. data/lib/pay/paddle/subscription.rb +59 -0
  21. data/lib/pay/paddle/webhooks.rb +1 -0
  22. data/lib/pay/paddle/webhooks/signature_verifier.rb +115 -0
  23. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +18 -0
  24. data/lib/pay/paddle/webhooks/subscription_created.rb +59 -0
  25. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +21 -0
  26. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +64 -0
  27. data/lib/pay/paddle/webhooks/subscription_updated.rb +34 -0
  28. data/lib/pay/stripe/billable.rb +6 -0
  29. data/lib/pay/stripe/charge.rb +4 -0
  30. data/lib/pay/stripe/subscription.rb +6 -0
  31. data/lib/pay/stripe/webhooks/charge_succeeded.rb +7 -7
  32. data/lib/pay/stripe/webhooks/payment_action_required.rb +7 -8
  33. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
  34. data/lib/pay/version.rb +1 -1
  35. metadata +59 -4
@@ -0,0 +1,18 @@
1
+ module Pay
2
+ module Paddle
3
+ module Webhooks
4
+ class SubscriptionCancelled
5
+ def initialize(data)
6
+ subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id: data["subscription_id"])
7
+
8
+ # We couldn't find the subscription for some reason, maybe it's from another service
9
+ return if subscription.nil?
10
+
11
+ # User canceled subscriptions have an ends_at
12
+ # Automatically canceled subscriptions need this value set
13
+ subscription.update!(ends_at: DateTime.parse(data["cancellation_effective_date"])) if subscription.ends_at.blank? && data["cancellation_effective_date"].present?
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ module Pay
2
+ module Paddle
3
+ module Webhooks
4
+ class SubscriptionCreated
5
+ def initialize(data)
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: data["subscription_id"])
8
+
9
+ # Create the subscription in the database if we don't have it already
10
+ if subscription.nil?
11
+
12
+ # The customer could already be in the database
13
+ owner = Pay.find_billable(processor: :paddle, processor_id: data["user_id"])
14
+
15
+ if owner.nil?
16
+ owner = owner_by_passtrough(data["passthrough"], data["subscription_plan_id"])
17
+ owner&.update!(processor: "paddle", processor_id: data["user_id"])
18
+ end
19
+
20
+ if owner.nil?
21
+ Rails.logger.error("[Pay] Unable to find Pay::Billable with owner: '#{data["passthrough"]}'. Searched these models: #{Pay.billable_models.join(", ")}")
22
+ return
23
+ end
24
+
25
+ subscription = Pay.subscription_model.new(owner: owner, name: "default", processor: "paddle", processor_id: data["subscription_id"], status: :active)
26
+ end
27
+
28
+ subscription.quantity = data["quantity"]
29
+ subscription.processor_plan = data["subscription_plan_id"]
30
+ subscription.paddle_update_url = data["update_url"]
31
+ subscription.paddle_cancel_url = data["cancel_url"]
32
+ subscription.trial_ends_at = Time.zone.parse(data["next_bill_date"]) if data["status"] == "trialing"
33
+
34
+ # If user was on trial, their subscription ends at the end of the trial
35
+ subscription.ends_at = if ["paused", "deleted"].include?(data["status"]) && subscription.on_trial?
36
+ subscription.trial_ends_at
37
+
38
+ # User wasn't on trial, so subscription ends at period end
39
+ elsif ["paused", "deleted"].include?(data["status"])
40
+ Time.zone.parse(data["next_bill_date"])
41
+
42
+ # Subscription isn't marked to cancel at period end
43
+ end
44
+
45
+ subscription.save!
46
+ end
47
+
48
+ private
49
+
50
+ def owner_by_passtrough(passthrough, product_id)
51
+ passthrough_json = JSON.parse(passthrough)
52
+ GlobalID::Locator.locate_signed(passthrough_json["owner_sgid"])
53
+ rescue JSON::ParserError
54
+ nil
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,21 @@
1
+ module Pay
2
+ module Paddle
3
+ module Webhooks
4
+ class SubscriptionPaymentRefunded
5
+ def initialize(data)
6
+ charge = Pay.charge_model.find_by(processor: :paddle, processor_id: data["subscription_payment_id"])
7
+ return unless charge.present?
8
+
9
+ charge.update(amount_refunded: Integer(data["gross_refund"].to_f * 100))
10
+ notify_user(charge.owner, charge)
11
+ end
12
+
13
+ def notify_user(user, charge)
14
+ if Pay.send_emails
15
+ Pay::UserMailer.refund(user, charge).deliver_later
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ module Pay
2
+ module Paddle
3
+ module Webhooks
4
+ class SubscriptionPaymentSucceeded
5
+ def initialize(data)
6
+ billable = Pay.find_billable(processor: :paddle, processor_id: data["user_id"])
7
+ return unless billable.present?
8
+ return if billable.charges.where(processor_id: data["subscription_payment_id"]).any?
9
+
10
+ charge = create_charge(billable, data)
11
+ notify_user(billable, charge)
12
+ end
13
+
14
+ def create_charge(user, data)
15
+ charge = user.charges.find_or_initialize_by(
16
+ processor: :paddle,
17
+ processor_id: data["subscription_payment_id"]
18
+ )
19
+
20
+ params = {
21
+ amount: Integer(data["sale_gross"].to_f * 100),
22
+ card_type: data["payment_method"],
23
+ paddle_receipt_url: data["receipt_url"],
24
+ created_at: DateTime.parse(data["event_time"])
25
+ }.merge(payment_params(data["subscription_id"]))
26
+
27
+ charge.update(params)
28
+
29
+ charge
30
+ end
31
+
32
+ def notify_user(user, charge)
33
+ if Pay.send_emails && charge.respond_to?(:receipt)
34
+ Pay::UserMailer.receipt(user, charge).deliver_later
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def payment_params(subscription_id)
41
+ subscription_user = PaddlePay::Subscription::User.list({subscription_id: subscription_id}).try(:first)
42
+ payment_information = subscription_user ? subscription_user[:payment_information] : nil
43
+ return {} if payment_information.nil?
44
+
45
+ case payment_information[:payment_method]
46
+ when "card"
47
+ {
48
+ card_type: payment_information[:card_type],
49
+ card_last4: payment_information[:last_four_digits],
50
+ card_exp_month: payment_information[:expiry_date].split("/").first,
51
+ card_exp_year: payment_information[:expiry_date].split("/").last
52
+ }
53
+ when "paypal"
54
+ {
55
+ card_type: "PayPal"
56
+ }
57
+ else
58
+ {}
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ module Pay
2
+ module Paddle
3
+ module Webhooks
4
+ class SubscriptionUpdated
5
+ def initialize(data)
6
+ subscription = Pay.subscription_model.find_by(processor: :paddle, processor_id: data["subscription_id"])
7
+
8
+ return if subscription.nil?
9
+
10
+ subscription.status = data["status"] == "deleted" ? "canceled" : data["status"]
11
+ subscription.quantity = data["new_quantity"]
12
+ subscription.processor_plan = data["subscription_plan_id"]
13
+ subscription.paddle_update_url = data["update_url"]
14
+ subscription.paddle_cancel_url = data["cancel_url"]
15
+
16
+ subscription.trial_ends_at = DateTime.parse(data["next_bill_date"]) if data["status"] == "trialing"
17
+
18
+ # If user was on trial, their subscription ends at the end of the trial
19
+ subscription.ends_at = if ["paused", "deleted"].include?(data["status"]) && subscription.on_trial?
20
+ subscription.trial_ends_at
21
+
22
+ # User wasn't on trial, so subscription ends at period end
23
+ elsif ["paused", "deleted"].include?(data["status"])
24
+ DateTime.parse(data["next_bill_date"])
25
+
26
+ # Subscription isn't marked to cancel at period end
27
+ end
28
+
29
+ subscription.save!
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,6 +1,12 @@
1
1
  module Pay
2
2
  module Stripe
3
3
  module Billable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :stripe, -> { where(processor: :stripe) }
8
+ end
9
+
4
10
  # Handles Billable#customer
5
11
  #
6
12
  # Returns Stripe::Customer
@@ -3,6 +3,10 @@ module Pay
3
3
  module Charge
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ included do
7
+ scope :stripe, -> { where(processor: :stripe) }
8
+ end
9
+
6
10
  def stripe?
7
11
  processor == "stripe"
8
12
  end
@@ -1,6 +1,12 @@
1
1
  module Pay
2
2
  module Stripe
3
3
  module Subscription
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :stripe, -> { where(processor: :stripe) }
8
+ end
9
+
4
10
  def stripe?
5
11
  processor == "stripe"
6
12
  end
@@ -9,13 +9,11 @@ module Pay
9
9
  return unless billable.present?
10
10
  return if billable.charges.where(processor_id: object.id).any?
11
11
 
12
- charge = create_charge(billable, object)
13
- notify_user(billable, charge)
14
- charge
12
+ create_charge(billable, object)
15
13
  end
16
14
 
17
- def create_charge(user, object)
18
- charge = user.charges.find_or_initialize_by(
15
+ def create_charge(billable, object)
16
+ charge = billable.charges.find_or_initialize_by(
19
17
  processor: :stripe,
20
18
  processor_id: object.id
21
19
  )
@@ -29,12 +27,14 @@ module Pay
29
27
  created_at: Time.zone.at(object.created)
30
28
  )
31
29
 
30
+ notify_user(billable, charge)
31
+
32
32
  charge
33
33
  end
34
34
 
35
- def notify_user(user, charge)
35
+ def notify_user(billable, charge)
36
36
  if Pay.send_emails && charge.respond_to?(:receipt)
37
- Pay::UserMailer.receipt(user, charge).deliver_later
37
+ Pay::UserMailer.receipt(billable, charge).deliver_later
38
38
  end
39
39
  end
40
40
  end
@@ -6,19 +6,18 @@ module Pay
6
6
  # Event is of type "invoice" see:
7
7
  # https://stripe.com/docs/api/invoices/object
8
8
 
9
- user = event.data.object.customer
9
+ object = event.data.object
10
10
 
11
- subscription = Pay.subscription_model.find_by(
12
- processor: :stripe,
13
- processor_id: event.data.object.subscription
14
- )
11
+ subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.subscription)
12
+ return if subscription.nil?
13
+ billable = subscription.owner
15
14
 
16
- notify_user(user, event.data.object.payment_intent, subscription)
15
+ notify_user(billable, event.data.object.payment_intent, subscription)
17
16
  end
18
17
 
19
- def notify_user(user, payment_intent_id, subscription)
18
+ def notify_user(billable, payment_intent_id, subscription)
20
19
  if Pay.send_emails
21
- Pay::UserMailer.payment_action_required(user, payment_intent_id, subscription).deliver_later
20
+ Pay::UserMailer.payment_action_required(billable, payment_intent_id, subscription).deliver_later
22
21
  end
23
22
  end
24
23
  end
@@ -18,7 +18,7 @@ module Pay
18
18
  return
19
19
  end
20
20
 
21
- subscription = Pay.subscription_model.new(owner: owner)
21
+ subscription = Pay.subscription_model.new(name: "default", owner: owner, processor: :stripe, processor_id: object.id)
22
22
  end
23
23
 
24
24
  subscription.quantity = object.quantity
@@ -1,3 +1,3 @@
1
1
  module Pay
2
- VERSION = "2.2.2"
2
+ VERSION = "2.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: 2.2.2
4
+ version: 2.3.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: 2020-11-11 00:00:00.000000000 Z
12
+ date: 2020-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -34,7 +34,7 @@ dependencies:
34
34
  version: 2.92.0
35
35
  - - "<"
36
36
  - !ruby/object:Gem::Version
37
- version: '3.0'
37
+ version: '4.0'
38
38
  type: :development
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: 2.92.0
45
45
  - - "<"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.0'
47
+ version: '4.0'
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: stripe
50
50
  requirement: !ruby/object:Gem::Requirement
@@ -73,6 +73,20 @@ dependencies:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '2.3'
76
+ - !ruby/object:Gem::Dependency
77
+ name: paddle_pay
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.1
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.0.1
76
90
  - !ruby/object:Gem::Dependency
77
91
  name: byebug
78
92
  requirement: !ruby/object:Gem::Requirement
@@ -157,6 +171,34 @@ dependencies:
157
171
  - - "~>"
158
172
  - !ruby/object:Gem::Version
159
173
  version: '1.4'
174
+ - !ruby/object:Gem::Dependency
175
+ name: mysql2
176
+ requirement: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ type: :development
182
+ prerelease: false
183
+ version_requirements: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ - !ruby/object:Gem::Dependency
189
+ name: pg
190
+ requirement: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ type: :development
196
+ prerelease: false
197
+ version_requirements: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
160
202
  - !ruby/object:Gem::Dependency
161
203
  name: vcr
162
204
  requirement: !ruby/object:Gem::Requirement
@@ -202,6 +244,7 @@ files:
202
244
  - app/controllers/pay/application_controller.rb
203
245
  - app/controllers/pay/payments_controller.rb
204
246
  - app/controllers/pay/webhooks/braintree_controller.rb
247
+ - app/controllers/pay/webhooks/paddle_controller.rb
205
248
  - app/helpers/pay/application_helper.rb
206
249
  - app/jobs/pay/application_job.rb
207
250
  - app/jobs/pay/email_sync_job.rb
@@ -221,6 +264,7 @@ files:
221
264
  - db/migrate/20170205020145_create_pay_subscriptions.rb
222
265
  - db/migrate/20170727235816_create_pay_charges.rb
223
266
  - db/migrate/20190816015720_add_status_to_pay_subscriptions.rb
267
+ - db/migrate/20200603134434_add_data_to_pay_models.rb
224
268
  - lib/generators/active_record/pay_generator.rb
225
269
  - lib/generators/active_record/templates/migration.rb
226
270
  - lib/generators/pay/email_views_generator.rb
@@ -236,6 +280,17 @@ files:
236
280
  - lib/pay/braintree/subscription.rb
237
281
  - lib/pay/engine.rb
238
282
  - lib/pay/env.rb
283
+ - lib/pay/paddle.rb
284
+ - lib/pay/paddle/billable.rb
285
+ - lib/pay/paddle/charge.rb
286
+ - lib/pay/paddle/subscription.rb
287
+ - lib/pay/paddle/webhooks.rb
288
+ - lib/pay/paddle/webhooks/signature_verifier.rb
289
+ - lib/pay/paddle/webhooks/subscription_cancelled.rb
290
+ - lib/pay/paddle/webhooks/subscription_created.rb
291
+ - lib/pay/paddle/webhooks/subscription_payment_refunded.rb
292
+ - lib/pay/paddle/webhooks/subscription_payment_succeeded.rb
293
+ - lib/pay/paddle/webhooks/subscription_updated.rb
239
294
  - lib/pay/payment.rb
240
295
  - lib/pay/receipts.rb
241
296
  - lib/pay/stripe.rb