pay 2.7.0 → 3.0.1

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -696
  3. data/app/controllers/pay/webhooks/braintree_controller.rb +10 -3
  4. data/app/controllers/pay/webhooks/paddle_controller.rb +7 -8
  5. data/app/controllers/pay/webhooks/stripe_controller.rb +6 -3
  6. data/app/jobs/pay/{email_sync_job.rb → customer_sync_job.rb} +3 -4
  7. data/app/models/pay/application_record.rb +1 -5
  8. data/app/models/pay/charge.rb +31 -18
  9. data/app/models/pay/customer.rb +87 -0
  10. data/app/models/pay/merchant.rb +19 -0
  11. data/app/models/pay/payment_method.rb +41 -0
  12. data/app/models/pay/subscription.rb +42 -30
  13. data/app/models/pay/webhook.rb +36 -0
  14. data/app/views/layouts/pay/application.html.erb +2 -3
  15. data/app/views/pay/payments/show.html.erb +109 -81
  16. data/app/views/pay/user_mailer/receipt.html.erb +2 -2
  17. data/app/views/pay/user_mailer/refund.html.erb +2 -2
  18. data/config/locales/en.yml +1 -1
  19. data/db/migrate/1_create_pay_tables.rb +72 -0
  20. data/lib/generators/active_record/billable_generator.rb +44 -0
  21. data/lib/generators/active_record/merchant_generator.rb +44 -0
  22. data/lib/generators/active_record/templates/billable_migration.rb +17 -0
  23. data/lib/generators/active_record/templates/merchant_migration.rb +12 -0
  24. data/lib/generators/pay/{pay_generator.rb → billable_generator.rb} +2 -3
  25. data/lib/generators/pay/merchant_generator.rb +17 -0
  26. data/lib/generators/pay/orm_helpers.rb +10 -6
  27. data/lib/pay/adapter.rb +9 -0
  28. data/lib/pay/attributes.rb +74 -0
  29. data/lib/pay/billable/sync_customer.rb +30 -0
  30. data/lib/pay/braintree/billable.rb +133 -110
  31. data/lib/pay/braintree/payment_method.rb +42 -0
  32. data/lib/pay/braintree/subscription.rb +9 -12
  33. data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -1
  34. data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +4 -4
  35. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
  36. data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -1
  37. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +2 -2
  38. data/lib/pay/braintree/webhooks/subscription_went_active.rb +1 -1
  39. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +1 -1
  40. data/lib/pay/braintree.rb +3 -2
  41. data/lib/pay/engine.rb +6 -1
  42. data/lib/pay/env.rb +8 -0
  43. data/lib/pay/fake_processor/billable.rb +45 -21
  44. data/lib/pay/fake_processor/payment_method.rb +21 -0
  45. data/lib/pay/fake_processor/subscription.rb +11 -8
  46. data/lib/pay/fake_processor.rb +2 -1
  47. data/lib/pay/nano_id.rb +13 -0
  48. data/lib/pay/paddle/billable.rb +18 -48
  49. data/lib/pay/paddle/charge.rb +5 -5
  50. data/lib/pay/paddle/payment_method.rb +60 -0
  51. data/lib/pay/paddle/response.rb +0 -0
  52. data/lib/pay/paddle/subscription.rb +49 -8
  53. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
  54. data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
  55. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  56. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +26 -28
  57. data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
  58. data/lib/pay/paddle.rb +7 -3
  59. data/lib/pay/payment.rb +1 -1
  60. data/lib/pay/receipts.rb +35 -7
  61. data/lib/pay/stripe/billable.rb +80 -102
  62. data/lib/pay/stripe/charge.rb +59 -3
  63. data/lib/pay/stripe/merchant.rb +10 -10
  64. data/lib/pay/stripe/payment_method.rb +61 -0
  65. data/lib/pay/stripe/subscription.rb +70 -8
  66. data/lib/pay/stripe/webhooks/account_updated.rb +2 -3
  67. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
  68. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
  69. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +15 -0
  70. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +15 -0
  71. data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
  72. data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
  73. data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
  74. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -14
  75. data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
  76. data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
  77. data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
  78. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -36
  79. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -9
  80. data/lib/pay/stripe/webhooks/subscription_renewing.rb +14 -6
  81. data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -29
  82. data/lib/pay/stripe.rb +12 -3
  83. data/lib/pay/version.rb +1 -1
  84. data/lib/pay/webhooks/delegator.rb +4 -0
  85. data/lib/pay/webhooks/process_job.rb +9 -0
  86. data/lib/pay/webhooks.rb +1 -0
  87. data/lib/pay.rb +7 -78
  88. data/lib/tasks/pay.rake +20 -0
  89. metadata +31 -39
  90. data/app/models/pay.rb +0 -5
  91. data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
  92. data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
  93. data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
  94. data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -15
  95. data/db/migrate/20210309004259_add_data_to_pay_billable.rb +0 -19
  96. data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +0 -5
  97. data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +0 -7
  98. data/lib/generators/active_record/pay_generator.rb +0 -58
  99. data/lib/generators/active_record/templates/migration.rb +0 -9
  100. data/lib/pay/billable/sync_email.rb +0 -40
  101. data/lib/pay/billable.rb +0 -172
@@ -0,0 +1,42 @@
1
+ module Pay
2
+ module Braintree
3
+ class PaymentMethod
4
+ attr_reader :pay_payment_method
5
+
6
+ delegate :customer, :processor_id, to: :pay_payment_method
7
+
8
+ def self.sync(id, object: nil, try: 0, retries: 1)
9
+ object ||= gateway.payment_method.find(id)
10
+
11
+ pay_customer = Pay::Customer.find_by(processor: :braintree, processor_id: object.customer_id)
12
+ return unless pay_customer
13
+
14
+ pay_customer.save_payment_method(object, default: object.default?)
15
+ end
16
+
17
+ def initialize(pay_payment_method)
18
+ @pay_payment_method = pay_payment_method
19
+ end
20
+
21
+ # Sets payment method as default on Stripe
22
+ def make_default!
23
+ result = gateway.customer.update(customer.processor_id, default_payment_method_token: processor_id)
24
+ raise Pay::Braintree::Error, result unless result.success?
25
+ result.success?
26
+ end
27
+
28
+ # Remove payment method
29
+ def detach
30
+ result = gateway.payment_method.delete(processor_id)
31
+ raise Pay::Braintree::Error, result unless result.success?
32
+ result.success?
33
+ end
34
+
35
+ private
36
+
37
+ def gateway
38
+ Pay.braintree_gateway
39
+ end
40
+ end
41
+ end
42
+ end
@@ -4,11 +4,11 @@ module Pay
4
4
  attr_reader :pay_subscription
5
5
 
6
6
  delegate :active?,
7
+ :customer,
7
8
  :canceled?,
8
9
  :ends_at,
9
10
  :name,
10
11
  :on_trial?,
11
- :owner,
12
12
  :processor_id,
13
13
  :processor_plan,
14
14
  :processor_subscription,
@@ -45,13 +45,13 @@ module Pay
45
45
 
46
46
  def cancel_now!
47
47
  gateway.subscription.cancel(processor_subscription.id)
48
- pay_subscription.update(status: :canceled, ends_at: Time.zone.now)
48
+ pay_subscription.update(status: :canceled, ends_at: Time.current)
49
49
  rescue ::Braintree::BraintreeError => e
50
50
  raise Pay::Braintree::Error, e
51
51
  end
52
52
 
53
53
  def on_grace_period?
54
- canceled? && Time.zone.now < ends_at
54
+ canceled? && Time.current < ends_at
55
55
  end
56
56
 
57
57
  def paused?
@@ -70,7 +70,7 @@ module Pay
70
70
  if canceled? && on_trial?
71
71
  duration = trial_ends_at.to_date - Date.today
72
72
 
73
- owner.subscribe(
73
+ customer.subscribe(
74
74
  name: name,
75
75
  plan: processor_plan,
76
76
  trial_period: true,
@@ -92,13 +92,15 @@ module Pay
92
92
  end
93
93
 
94
94
  def swap(plan)
95
+ raise ArgumentError, "plan must be a string" unless plan.is_a?(String)
96
+
95
97
  if on_grace_period? && processor_plan == plan
96
98
  resume
97
99
  return
98
100
  end
99
101
 
100
102
  unless active?
101
- owner.subscribe(name: name, plan: plan, trial_period: false)
103
+ customer.subscribe(name: name, plan: plan, trial_period: false)
102
104
  return
103
105
  end
104
106
 
@@ -120,12 +122,7 @@ module Pay
120
122
  prorate_charges: prorate?
121
123
  }
122
124
  })
123
-
124
- if result.success?
125
- pay_subscription.update(status: :active, processor_plan: braintree_plan.id, ends_at: nil)
126
- else
127
- raise Error, "Braintree failed to swap plans: #{result.message}"
128
- end
125
+ raise Error, "Braintree failed to swap plans: #{result.message}" unless result.success?
129
126
  rescue ::Braintree::BraintreeError => e
130
127
  raise Pay::Braintree::Error, e
131
128
  end
@@ -205,7 +202,7 @@ module Pay
205
202
 
206
203
  cancel_now!
207
204
 
208
- owner.subscribe(**options.merge(name: name, plan: plan.id))
205
+ customer.subscribe(**options.merge(name: name, plan: plan.id))
209
206
  end
210
207
  end
211
208
  end
@@ -8,7 +8,7 @@ module Pay
8
8
  subscription = event.subscription
9
9
  return if subscription.nil?
10
10
 
11
- pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
11
+ pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
12
  return unless pay_subscription.present?
13
13
 
14
14
  pay_subscription.update!(ends_at: Time.current, status: :canceled)
@@ -8,14 +8,14 @@ module Pay
8
8
  subscription = event.subscription
9
9
  return if subscription.nil?
10
10
 
11
- pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
11
+ pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
12
  return unless pay_subscription.present?
13
13
 
14
- billable = pay_subscription.owner
15
- charge = Pay::Braintree::Billable.new(billable).save_transaction(subscription.transactions.first)
14
+ pay_customer = pay_subscription.customer
15
+ charge = Pay::Braintree::Billable.new(pay_customer).save_transaction(subscription.transactions.first)
16
16
 
17
17
  if Pay.send_emails
18
- Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
18
+ Pay::UserMailer.with(billable: pay_customer.owner, charge: charge).receipt.deliver_later
19
19
  end
20
20
  end
21
21
  end
@@ -8,7 +8,7 @@ module Pay
8
8
  subscription = event.subscription
9
9
  return if subscription.nil?
10
10
 
11
- pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
11
+ pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
12
  return unless pay_subscription.present?
13
13
 
14
14
  # billable = pay_subscription.owner
@@ -8,7 +8,7 @@ module Pay
8
8
  subscription = event.subscription
9
9
  return if subscription.nil?
10
10
 
11
- pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
11
+ pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
12
  return unless pay_subscription.present?
13
13
 
14
14
  pay_subscription.update!(ends_at: Time.current, status: :canceled)
@@ -8,10 +8,10 @@ module Pay
8
8
  subscription = event.subscription
9
9
  return if subscription.nil?
10
10
 
11
- pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
11
+ pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
12
  return unless pay_subscription.present?
13
13
 
14
- pay_subscription.update(trial_ends_at: Time.zone.now)
14
+ pay_subscription.update(trial_ends_at: Time.current)
15
15
  end
16
16
  end
17
17
  end
@@ -8,7 +8,7 @@ module Pay
8
8
  subscription = event.subscription
9
9
  return if subscription.nil?
10
10
 
11
- pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
11
+ pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
12
  return unless pay_subscription.present?
13
13
 
14
14
  pay_subscription.update!(status: :active)
@@ -8,7 +8,7 @@ module Pay
8
8
  subscription = event.subscription
9
9
  return if subscription.nil?
10
10
 
11
- pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
11
+ pay_subscription = Pay::Subscription.find_by_processor_and_id(:braintree, subscription.id)
12
12
  return unless pay_subscription.present?
13
13
 
14
14
  pay_subscription.update!(status: :past_due)
data/lib/pay/braintree.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  module Pay
2
2
  module Braintree
3
+ autoload :AuthorizationError, "pay/braintree/authorization_error"
3
4
  autoload :Billable, "pay/braintree/billable"
4
5
  autoload :Charge, "pay/braintree/charge"
5
- autoload :Subscription, "pay/braintree/subscription"
6
6
  autoload :Error, "pay/braintree/error"
7
- autoload :AuthorizationError, "pay/braintree/authorization_error"
7
+ autoload :PaymentMethod, "pay/braintree/payment_method"
8
+ autoload :Subscription, "pay/braintree/subscription"
8
9
 
9
10
  module Webhooks
10
11
  autoload :SubscriptionCanceled, "pay/braintree/webhooks/subscription_canceled"
data/lib/pay/engine.rb CHANGED
@@ -10,6 +10,11 @@ module Pay
10
10
  mount Pay::Engine, at: Pay.routes_path, as: "pay"
11
11
  end
12
12
  end
13
+
14
+ # Include the pay attributes for ActiveRecord models
15
+ ActiveSupport.on_load(:active_record) do
16
+ include Pay::Attributes
17
+ end
13
18
  end
14
19
 
15
20
  config.to_prepare do
@@ -17,7 +22,7 @@ module Pay
17
22
  Pay::Braintree.setup if defined? ::Braintree
18
23
  Pay::Paddle.setup if defined? ::PaddlePay
19
24
 
20
- Pay.charge_model.include Pay::Receipts if defined? ::Receipts::Receipt
25
+ Pay::Charge.include Pay::Receipts if defined? ::Receipts::Receipt
21
26
  end
22
27
  end
23
28
  end
data/lib/pay/env.rb CHANGED
@@ -17,6 +17,14 @@ module Pay
17
17
  secrets&.dig(env, scope, name) ||
18
18
  credentials&.dig(scope, name) ||
19
19
  secrets&.dig(scope, name)
20
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage
21
+ Rails.logger.error <<~MESSAGE
22
+ Rails was unable to decrypt credentials. Pay checks the Rails credentials to look for API keys for payment processors.
23
+
24
+ Make sure to set the `RAILS_MASTER_KEY` env variable or in the .key file. To learn more, run "bin/rails credentials:help"
25
+
26
+ If you're not using Rails credentials, you can delete `config/credentials.yml.enc` and `config/credentials/`.
27
+ MESSAGE
20
28
  end
21
29
 
22
30
  def env
@@ -1,46 +1,70 @@
1
1
  module Pay
2
2
  module FakeProcessor
3
3
  class Billable
4
- attr_reader :billable
4
+ attr_reader :pay_customer
5
5
 
6
6
  delegate :processor_id,
7
7
  :processor_id?,
8
8
  :email,
9
9
  :customer_name,
10
10
  :card_token,
11
- to: :billable
11
+ to: :pay_customer
12
12
 
13
- def initialize(billable)
14
- @billable = billable
13
+ def initialize(pay_customer)
14
+ @pay_customer = pay_customer
15
15
  end
16
16
 
17
17
  def customer
18
- billable
18
+ pay_customer.update!(processor_id: NanoId.generate) unless processor_id?
19
+ pay_customer
19
20
  end
20
21
 
21
22
  def charge(amount, options = {})
22
- billable.charges.create(
23
- processor: :fake_processor,
24
- processor_id: rand(100_000_000),
23
+ # Make to generate a processor_id
24
+ customer
25
+
26
+ attributes = options.merge(
27
+ processor_id: NanoId.generate,
25
28
  amount: amount,
26
- card_type: :fake,
27
- card_last4: 1234,
28
- card_exp_month: Date.today.month,
29
- card_exp_year: Date.today.year
29
+ data: {
30
+ kind: :card,
31
+ type: :fake,
32
+ last4: 1234,
33
+ exp_month: Date.today.month,
34
+ exp_year: Date.today.year
35
+ }
30
36
  )
37
+ pay_customer.charges.create!(attributes)
31
38
  end
32
39
 
33
40
  def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
34
- subscription = OpenStruct.new id: rand(1_000_000)
35
- billable.create_pay_subscription(subscription, :fake_processor, name, plan, status: :active, quantity: options.fetch(:quantity, 1))
41
+ # Make to generate a processor_id
42
+ customer
43
+
44
+ attributes = options.merge(
45
+ processor_id: NanoId.generate,
46
+ name: name,
47
+ processor_plan: plan,
48
+ status: :active,
49
+ quantity: options.fetch(:quantity, 1)
50
+ )
51
+ pay_customer.subscriptions.create!(attributes)
36
52
  end
37
53
 
38
- def update_card(payment_method_id)
39
- billable.update(
40
- card_type: :fake,
41
- card_last4: 1234,
42
- card_exp_month: Date.today.month,
43
- card_exp_year: Date.today.year
54
+ def add_payment_method(payment_method_id, default: false)
55
+ # Make to generate a processor_id
56
+ customer
57
+
58
+ pay_customer.payment_methods.create!(
59
+ processor_id: NanoId.generate,
60
+ default: default,
61
+ type: :fake,
62
+ data: {
63
+ brand: "Fake",
64
+ last4: 1234,
65
+ exp_month: Date.today.month,
66
+ exp_year: Date.today.year
67
+ }
44
68
  )
45
69
  end
46
70
 
@@ -49,7 +73,7 @@ module Pay
49
73
  end
50
74
 
51
75
  def processor_subscription(subscription_id, options = {})
52
- billable.subscriptions.find_by(processor: :fake_processor, processor_id: subscription_id)
76
+ pay_customer.subscriptions.find_by(processor_id: subscription_id)
53
77
  end
54
78
 
55
79
  def trial_end_date(subscription)
@@ -0,0 +1,21 @@
1
+ module Pay
2
+ module FakeProcessor
3
+ class PaymentMethod
4
+ attr_reader :pay_payment_method
5
+
6
+ delegate :customer, :processor_id, to: :pay_payment_method
7
+
8
+ def initialize(pay_payment_method)
9
+ @pay_payment_method = pay_payment_method
10
+ end
11
+
12
+ # Sets payment method as default on Stripe
13
+ def make_default!
14
+ end
15
+
16
+ # Remove payment method
17
+ def detach
18
+ end
19
+ end
20
+ end
21
+ end
@@ -23,8 +23,14 @@ module Pay
23
23
  pay_subscription
24
24
  end
25
25
 
26
+ # With trial, sets end to trial end (mimicing Stripe)
27
+ # Without trial, sets can ends_at to end of month
26
28
  def cancel
27
- pay_subscription.update(ends_at: Time.current.end_of_month)
29
+ if pay_subscription.on_trial?
30
+ pay_subscription.update(ends_at: pay_subscription.trial_ends_at)
31
+ else
32
+ pay_subscription.update(ends_at: Time.current.end_of_month)
33
+ end
28
34
  end
29
35
 
30
36
  def cancel_now!
@@ -32,27 +38,24 @@ module Pay
32
38
  end
33
39
 
34
40
  def on_grace_period?
35
- canceled? && Time.zone.now < ends_at
41
+ canceled? && Time.current < ends_at
36
42
  end
37
43
 
38
44
  def paused?
39
- false
45
+ pay_subscription.status == "paused"
40
46
  end
41
47
 
42
48
  def pause
43
- raise NotImplementedError, "FakeProcessor does not support pausing subscriptions"
49
+ pay_subscription.update(status: :paused, trial_ends_at: Time.current)
44
50
  end
45
51
 
46
52
  def resume
47
- unless on_grace_period?
53
+ unless on_grace_period? || paused?
48
54
  raise StandardError, "You can only resume subscriptions within their grace period."
49
55
  end
50
-
51
- pay_subscription.update(ends_at: nil, status: :active)
52
56
  end
53
57
 
54
58
  def swap(plan)
55
- pay_subscription.update(processor_plan: plan)
56
59
  end
57
60
  end
58
61
  end
@@ -2,7 +2,8 @@ module Pay
2
2
  module FakeProcessor
3
3
  autoload :Billable, "pay/fake_processor/billable"
4
4
  autoload :Charge, "pay/fake_processor/charge"
5
- autoload :Subscription, "pay/fake_processor/subscription"
6
5
  autoload :Error, "pay/fake_processor/error"
6
+ autoload :PaymentMethod, "pay/fake_processor/payment_method"
7
+ autoload :Subscription, "pay/fake_processor/subscription"
7
8
  end
8
9
  end
@@ -0,0 +1,13 @@
1
+ module Pay
2
+ module NanoId
3
+ # Generates unique IDs - faster than UUID
4
+ ALPHABET = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze
5
+ ALPHABET_SIZE = ALPHABET.size
6
+
7
+ def self.generate(size: 21)
8
+ id = ""
9
+ size.times { id << ALPHABET[(Random.rand * ALPHABET_SIZE).floor] }
10
+ id
11
+ end
12
+ end
13
+ end