pay 2.2.2 → 2.4.3

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +142 -9
  3. data/Rakefile +2 -4
  4. data/app/controllers/pay/payments_controller.rb +2 -0
  5. data/app/controllers/pay/webhooks/braintree_controller.rb +1 -1
  6. data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
  7. data/app/mailers/pay/user_mailer.rb +14 -35
  8. data/app/models/pay/application_record.rb +6 -1
  9. data/app/models/pay/charge.rb +7 -0
  10. data/app/models/pay/subscription.rb +23 -8
  11. data/app/views/pay/user_mailer/payment_action_required.html.erb +1 -1
  12. data/app/views/pay/user_mailer/receipt.html.erb +6 -6
  13. data/app/views/pay/user_mailer/refund.html.erb +6 -6
  14. data/app/views/pay/user_mailer/subscription_renewing.html.erb +1 -1
  15. data/config/locales/en.yml +137 -0
  16. data/config/routes.rb +1 -0
  17. data/db/migrate/20200603134434_add_data_to_pay_models.rb +22 -0
  18. data/lib/pay.rb +9 -41
  19. data/lib/pay/billable.rb +14 -9
  20. data/lib/pay/braintree/billable.rb +21 -15
  21. data/lib/pay/braintree/charge.rb +7 -3
  22. data/lib/pay/braintree/subscription.rb +22 -8
  23. data/lib/pay/engine.rb +7 -0
  24. data/lib/pay/errors.rb +73 -0
  25. data/lib/pay/paddle.rb +38 -0
  26. data/lib/pay/paddle/billable.rb +95 -0
  27. data/lib/pay/paddle/charge.rb +39 -0
  28. data/lib/pay/paddle/subscription.rb +68 -0
  29. data/lib/pay/paddle/webhooks.rb +1 -0
  30. data/lib/pay/paddle/webhooks/signature_verifier.rb +115 -0
  31. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +18 -0
  32. data/lib/pay/paddle/webhooks/subscription_created.rb +59 -0
  33. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +21 -0
  34. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +43 -0
  35. data/lib/pay/paddle/webhooks/subscription_updated.rb +37 -0
  36. data/lib/pay/receipts.rb +6 -6
  37. data/lib/pay/stripe.rb +3 -0
  38. data/lib/pay/stripe/billable.rb +10 -4
  39. data/lib/pay/stripe/charge.rb +6 -2
  40. data/lib/pay/stripe/subscription.rb +21 -7
  41. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -2
  42. data/lib/pay/stripe/webhooks/charge_succeeded.rb +7 -7
  43. data/lib/pay/stripe/webhooks/payment_action_required.rb +7 -8
  44. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
  45. data/lib/pay/stripe/webhooks/subscription_renewing.rb +4 -3
  46. data/lib/pay/version.rb +1 -1
  47. metadata +32 -40
@@ -1,6 +1,12 @@
1
1
  module Pay
2
2
  module Braintree
3
3
  module Billable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :braintree, -> { where(processor: :braintree) }
8
+ end
9
+
4
10
  # Handles Billable#customer
5
11
  #
6
12
  # Returns Braintree::Customer
@@ -14,7 +20,7 @@ module Pay
14
20
  last_name: try(:last_name),
15
21
  payment_method_nonce: card_token
16
22
  )
17
- raise BraintreeError.new(result), result.message unless result.success?
23
+ raise Pay::Braintree::Error, result unless result.success?
18
24
 
19
25
  update(processor: "braintree", processor_id: result.customer.id)
20
26
 
@@ -27,7 +33,7 @@ module Pay
27
33
  rescue ::Braintree::AuthorizationError
28
34
  raise BraintreeAuthorizationError
29
35
  rescue ::Braintree::BraintreeError => e
30
- raise BraintreeError, e.message
36
+ raise Pay::Braintree::Error, e
31
37
  end
32
38
 
33
39
  # Handles Billable#charge
@@ -35,19 +41,19 @@ module Pay
35
41
  # Returns a Pay::Charge
36
42
  def create_braintree_charge(amount, options = {})
37
43
  args = {
38
- amount: amount / 100.0,
44
+ amount: amount.to_i / 100.0,
39
45
  customer_id: customer.id,
40
46
  options: {submit_for_settlement: true}
41
47
  }.merge(options)
42
48
 
43
49
  result = gateway.transaction.sale(args)
44
- raise BraintreeError.new(result), result.message unless result.success?
50
+ raise Pay::Braintree::Error, result unless result.success?
45
51
 
46
52
  save_braintree_transaction(result.transaction)
47
53
  rescue ::Braintree::AuthorizationError
48
- raise BraintreeAuthorizationError
54
+ raise Pay::Braintree::AuthorizationError
49
55
  rescue ::Braintree::BraintreeError => e
50
- raise BraintreeError, e.message
56
+ raise Pay::Braintree::Error, e
51
57
  end
52
58
 
53
59
  # Handles Billable#subscribe
@@ -68,13 +74,13 @@ module Pay
68
74
  )
69
75
 
70
76
  result = gateway.subscription.create(subscription_options)
71
- raise BraintreeError.new(result), result.message unless result.success?
77
+ raise Pay::Braintree::Error, result unless result.success?
72
78
 
73
79
  create_subscription(result.subscription, "braintree", name, plan, status: :active)
74
80
  rescue ::Braintree::AuthorizationError
75
- raise BraintreeAuthorizationError
81
+ raise Pay::Braintree::AuthorizationError
76
82
  rescue ::Braintree::BraintreeError => e
77
- raise BraintreeError, e.message
83
+ raise Pay::Braintree::Error, e
78
84
  end
79
85
 
80
86
  # Handles Billable#update_card
@@ -89,15 +95,15 @@ module Pay
89
95
  verify_card: true
90
96
  }
91
97
  )
92
- raise BraintreeError.new(result), result.message unless result.success?
98
+ raise Pay::Braintree::Error, result unless result.success?
93
99
 
94
100
  update_braintree_card_on_file result.payment_method
95
101
  update_subscriptions_to_payment_method(result.payment_method.token)
96
102
  true
97
103
  rescue ::Braintree::AuthorizationError
98
- raise BraintreeAuthorizationError
104
+ raise Pay::Braintree::AuthorizationError
99
105
  rescue ::Braintree::BraintreeError => e
100
- raise BraintreeError, e.message
106
+ raise Pay::Braintree::Error, e
101
107
  end
102
108
 
103
109
  def update_braintree_email!
@@ -111,11 +117,11 @@ module Pay
111
117
  def braintree_trial_end_date(subscription)
112
118
  return unless subscription.trial_period
113
119
  # Braintree returns dates without time zones, so we'll assume they're UTC
114
- Time.parse(subscription.first_billing_date).end_of_day
120
+ subscription.first_billing_date.end_of_day
115
121
  end
116
122
 
117
123
  def update_subscriptions_to_payment_method(token)
118
- subscriptions.each do |subscription|
124
+ subscriptions.braintree.each do |subscription|
119
125
  if subscription.active?
120
126
  gateway.subscription.update(subscription.processor_id, {payment_method_token: token})
121
127
  end
@@ -175,7 +181,7 @@ module Pay
175
181
 
176
182
  def card_details_for_braintree_transaction(transaction)
177
183
  case transaction.payment_instrument_type
178
- when "credit_card", "samsung_pay_card", "masterpass_card", "samsung_pay_card", "visa_checkout_card"
184
+ when "credit_card", "samsung_pay_card", "masterpass_card", "visa_checkout_card"
179
185
  payment_method = transaction.send("#{transaction.payment_instrument_type}_details")
180
186
  {
181
187
  card_type: payment_method.card_type,
@@ -3,14 +3,18 @@ module Pay
3
3
  module Charge
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ included do
7
+ scope :braintree, -> { where(processor: :braintree) }
8
+ end
9
+
6
10
  def braintree?
7
11
  processor == "braintree"
8
12
  end
9
13
 
10
14
  def braintree_charge
11
15
  Pay.braintree_gateway.transaction.find(processor_id)
12
- rescue ::Braintree::BraintreeError => e
13
- raise Error, e.message
16
+ rescue ::Braintree::Braintree::Error => e
17
+ raise Pay::Braintree::Error, e
14
18
  end
15
19
 
16
20
  def braintree_refund!(amount_to_refund)
@@ -18,7 +22,7 @@ module Pay
18
22
 
19
23
  update(amount_refunded: amount_to_refund)
20
24
  rescue ::Braintree::BraintreeError => e
21
- raise Error, e.message
25
+ raise Pay::Braintree::Error, e
22
26
  end
23
27
  end
24
28
  end
@@ -1,9 +1,7 @@
1
1
  module Pay
2
2
  module Braintree
3
3
  module Subscription
4
- def braintree?
5
- processor == "braintree"
6
- end
4
+ extend ActiveSupport::Concern
7
5
 
8
6
  def braintree_cancel
9
7
  subscription = processor_subscription
@@ -18,17 +16,33 @@ module Pay
18
16
  update(status: :canceled, ends_at: subscription.billing_period_end_date.to_date)
19
17
  end
20
18
  rescue ::Braintree::BraintreeError => e
21
- raise Error, e.message
19
+ raise Pay::Braintree::Error, e
22
20
  end
23
21
 
24
22
  def braintree_cancel_now!
25
23
  gateway.subscription.cancel(processor_subscription.id)
26
24
  update(status: :canceled, ends_at: Time.zone.now)
27
25
  rescue ::Braintree::BraintreeError => e
28
- raise Error, e.message
26
+ raise Pay::Braintree::Error, e
27
+ end
28
+
29
+ def braintree_on_grace_period?
30
+ canceled? && Time.zone.now < ends_at
31
+ end
32
+
33
+ def braintree_paused?
34
+ false
35
+ end
36
+
37
+ def braintree_pause
38
+ raise NotImplementedError, "Braintree does not support pausing subscriptions"
29
39
  end
30
40
 
31
41
  def braintree_resume
42
+ unless on_grace_period?
43
+ raise StandardError, "You can only resume subscriptions within their grace period."
44
+ end
45
+
32
46
  if canceled? && on_trial?
33
47
  duration = trial_ends_at.to_date - Date.today
34
48
 
@@ -51,7 +65,7 @@ module Pay
51
65
 
52
66
  update(status: :active)
53
67
  rescue ::Braintree::BraintreeError => e
54
- raise Error, e.message
68
+ raise Pay::Braintree::Error, e
55
69
  end
56
70
 
57
71
  def braintree_swap(plan)
@@ -90,7 +104,7 @@ module Pay
90
104
  raise Error, "Braintree failed to swap plans: #{result.message}"
91
105
  end
92
106
  rescue ::Braintree::BraintreeError => e
93
- raise Error, e.message
107
+ raise Pay::Braintree::Error, e
94
108
  end
95
109
 
96
110
  private
@@ -168,7 +182,7 @@ module Pay
168
182
 
169
183
  cancel_now!
170
184
 
171
- owner.subscribe(options.merge(name: name, plan: plan.id))
185
+ owner.subscribe(**options.merge(name: name, plan: plan.id))
172
186
  end
173
187
  end
174
188
  end
data/lib/pay/engine.rb CHANGED
@@ -11,6 +11,11 @@ begin
11
11
  require "stripe_event"
12
12
  rescue LoadError
13
13
  end
14
+
15
+ begin
16
+ require "paddle_pay"
17
+ rescue LoadError
18
+ end
14
19
  # rubocop:enable Lint/HandleExceptions
15
20
 
16
21
  module Pay
@@ -21,6 +26,7 @@ module Pay
21
26
  # Include processor backends
22
27
  require "pay/stripe" if defined? ::Stripe
23
28
  require "pay/braintree" if defined? ::Braintree
29
+ require "pay/paddle" if defined? ::PaddlePay
24
30
 
25
31
  if Pay.automount_routes
26
32
  app.routes.append do
@@ -32,6 +38,7 @@ module Pay
32
38
  config.to_prepare do
33
39
  Pay::Stripe.setup if defined? ::Stripe
34
40
  Pay::Braintree.setup if defined? ::Braintree
41
+ Pay::Paddle.setup if defined? ::PaddlePay
35
42
 
36
43
  Pay.charge_model.include Pay::Receipts if defined? ::Receipts::Receipt
37
44
  end
data/lib/pay/errors.rb ADDED
@@ -0,0 +1,73 @@
1
+ module Pay
2
+ class Error < StandardError
3
+ attr_reader :result
4
+
5
+ def initialize(result = nil)
6
+ @result = result
7
+ end
8
+ end
9
+
10
+ class PaymentError < StandardError
11
+ attr_reader :payment
12
+
13
+ def initialize(payment)
14
+ @payment = payment
15
+ end
16
+ end
17
+
18
+ class ActionRequired < PaymentError
19
+ def message
20
+ I18n.t("errors.action_required")
21
+ end
22
+ end
23
+
24
+ class InvalidPaymentMethod < PaymentError
25
+ def message
26
+ I18n.t("errors.invalid_payment")
27
+ end
28
+ end
29
+
30
+ module Braintree
31
+ class Error < Error
32
+ def message
33
+ result.message
34
+ end
35
+ end
36
+
37
+ class AuthorizationError < Braintree::Error
38
+ def message
39
+ I18n.t("errors.braintree.authorization")
40
+ end
41
+ end
42
+ end
43
+
44
+ module Stripe
45
+ class Error < Error
46
+ def message
47
+ I18n.t("errors.stripe.#{result.code}", default: result.message)
48
+ end
49
+ end
50
+ end
51
+
52
+ module Paddle
53
+ class Error < Error
54
+ def message
55
+ I18n.t("errors.paddle.#{result.code}", default: result.message)
56
+ end
57
+ end
58
+ end
59
+
60
+ class BraintreeError < Braintree::Error
61
+ def message
62
+ ActiveSupport::Deprecation.warn("Pay::BraintreeError is deprecated. Instead, use `Pay::Braintree::Error`.")
63
+ super
64
+ end
65
+ end
66
+
67
+ class BraintreeAuthorizationError < BraintreeError
68
+ def message
69
+ ActiveSupport::Deprecation.warn("Pay::BraintreeAuthorizationError is deprecated. Instead, use `Pay::Braintree::AuthorizationError`.")
70
+ I18n.t("errors.braintree.authorization")
71
+ end
72
+ end
73
+ end
data/lib/pay/paddle.rb ADDED
@@ -0,0 +1,38 @@
1
+ require "pay/env"
2
+ require "pay/paddle/billable"
3
+ require "pay/paddle/charge"
4
+ require "pay/paddle/subscription"
5
+ require "pay/paddle/webhooks"
6
+
7
+ module Pay
8
+ module Paddle
9
+ include Env
10
+
11
+ extend self
12
+
13
+ def setup
14
+ ::PaddlePay.config.vendor_id = vendor_id
15
+ ::PaddlePay.config.vendor_auth_code = vendor_auth_code
16
+
17
+ Pay.charge_model.include Pay::Paddle::Charge
18
+ Pay.subscription_model.include Pay::Paddle::Subscription
19
+ Pay.billable_models.each { |model| model.include Pay::Paddle::Billable }
20
+ end
21
+
22
+ def vendor_id
23
+ find_value_by_name(:paddle, :vendor_id)
24
+ end
25
+
26
+ def vendor_auth_code
27
+ find_value_by_name(:paddle, :vendor_auth_code)
28
+ end
29
+
30
+ def public_key_base64
31
+ find_value_by_name(:paddle, :public_key_base64)
32
+ end
33
+
34
+ def passthrough(owner:, **options)
35
+ options.merge(owner_sgid: owner.to_sgid.to_s).to_json
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,95 @@
1
+ module Pay
2
+ module Paddle
3
+ module Billable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :paddle, -> { where(processor: :paddle) }
8
+ end
9
+
10
+ def paddle_customer
11
+ # pass
12
+ end
13
+
14
+ def create_paddle_charge(amount, options = {})
15
+ return unless subscription.processor_id
16
+ raise Pay::Error, "A charge_name is required to create a one-time charge" if options[:charge_name].nil?
17
+ response = PaddlePay::Subscription::Charge.create(subscription.processor_id, amount.to_f / 100, options[:charge_name], options)
18
+ charge = charges.find_or_initialize_by(
19
+ processor: :paddle,
20
+ processor_id: response[:invoice_id]
21
+ )
22
+ charge.update(
23
+ amount: Integer(response[:amount].to_f * 100),
24
+ card_type: subscription.processor_subscription.payment_information[:payment_method],
25
+ paddle_receipt_url: response[:receipt_url],
26
+ created_at: Time.zone.parse(response[:payment_date])
27
+ )
28
+ charge
29
+ rescue ::PaddlePay::PaddlePayError => e
30
+ raise Pay::Paddle::Error, e
31
+ end
32
+
33
+ def create_paddle_subscription(name, plan, options = {})
34
+ # pass
35
+ end
36
+
37
+ def update_paddle_card(token)
38
+ sync_payment_information_from_paddle
39
+ end
40
+
41
+ def update_paddle_email!
42
+ # pass
43
+ end
44
+
45
+ def paddle_trial_end_date(subscription)
46
+ return unless subscription.state == "trialing"
47
+ Time.zone.parse(subscription.next_payment[:date]).end_of_day
48
+ end
49
+
50
+ def paddle_subscription(subscription_id, options = {})
51
+ hash = PaddlePay::Subscription::User.list({subscription_id: subscription_id}, options).try(:first)
52
+ OpenStruct.new(hash)
53
+ rescue ::PaddlePay::PaddlePayError => e
54
+ raise Pay::Paddle::Error, e
55
+ end
56
+
57
+ def paddle_invoice!(options = {})
58
+ # pass
59
+ end
60
+
61
+ def paddle_upcoming_invoice
62
+ # pass
63
+ end
64
+
65
+ def sync_payment_information_from_paddle
66
+ payment_information = paddle_payment_information(subscription.processor_id)
67
+ update!(payment_information) unless payment_information.empty?
68
+ rescue ::PaddlePay::PaddlePayError => e
69
+ raise Pay::Paddle::Error, e
70
+ end
71
+
72
+ def paddle_payment_information(subscription_id)
73
+ subscription_user = PaddlePay::Subscription::User.list({subscription_id: subscription_id}).try(:first)
74
+ payment_information = subscription_user ? subscription_user[:payment_information] : nil
75
+ return {} if payment_information.nil?
76
+
77
+ case payment_information[:payment_method]
78
+ when "card"
79
+ {
80
+ card_type: payment_information[:card_type],
81
+ card_last4: payment_information[:last_four_digits],
82
+ card_exp_month: payment_information[:expiry_date].split("/").first,
83
+ card_exp_year: payment_information[:expiry_date].split("/").last
84
+ }
85
+ when "paypal"
86
+ {
87
+ card_type: "PayPal"
88
+ }
89
+ else
90
+ {}
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end