pay 2.7.1 → 3.0.2

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -715
  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 +54 -17
  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 +108 -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/templates/billable_migration.rb +1 -0
  21. data/lib/pay/attributes.rb +74 -0
  22. data/lib/pay/billable/sync_customer.rb +30 -0
  23. data/lib/pay/braintree/billable.rb +133 -110
  24. data/lib/pay/braintree/payment_method.rb +42 -0
  25. data/lib/pay/braintree/subscription.rb +9 -12
  26. data/lib/pay/braintree/webhooks/subscription_canceled.rb +1 -1
  27. data/lib/pay/braintree/webhooks/subscription_charged_successfully.rb +4 -4
  28. data/lib/pay/braintree/webhooks/subscription_charged_unsuccessfully.rb +1 -1
  29. data/lib/pay/braintree/webhooks/subscription_expired.rb +1 -1
  30. data/lib/pay/braintree/webhooks/subscription_trial_ended.rb +2 -2
  31. data/lib/pay/braintree/webhooks/subscription_went_active.rb +1 -1
  32. data/lib/pay/braintree/webhooks/subscription_went_past_due.rb +1 -1
  33. data/lib/pay/braintree.rb +3 -2
  34. data/lib/pay/engine.rb +6 -1
  35. data/lib/pay/fake_processor/billable.rb +45 -21
  36. data/lib/pay/fake_processor/payment_method.rb +21 -0
  37. data/lib/pay/fake_processor/subscription.rb +11 -8
  38. data/lib/pay/fake_processor.rb +2 -1
  39. data/lib/pay/nano_id.rb +13 -0
  40. data/lib/pay/paddle/billable.rb +18 -48
  41. data/lib/pay/paddle/charge.rb +5 -5
  42. data/lib/pay/paddle/payment_method.rb +60 -0
  43. data/lib/pay/paddle/response.rb +0 -0
  44. data/lib/pay/paddle/subscription.rb +49 -8
  45. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +6 -3
  46. data/lib/pay/paddle/webhooks/subscription_created.rb +1 -40
  47. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +3 -3
  48. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +26 -28
  49. data/lib/pay/paddle/webhooks/subscription_updated.rb +2 -2
  50. data/lib/pay/paddle.rb +7 -3
  51. data/lib/pay/payment.rb +1 -1
  52. data/lib/pay/receipts.rb +35 -7
  53. data/lib/pay/stripe/billable.rb +75 -76
  54. data/lib/pay/stripe/charge.rb +44 -17
  55. data/lib/pay/stripe/merchant.rb +10 -10
  56. data/lib/pay/stripe/payment_method.rb +61 -0
  57. data/lib/pay/stripe/subscription.rb +55 -22
  58. data/lib/pay/stripe/webhooks/account_updated.rb +2 -3
  59. data/lib/pay/stripe/webhooks/charge_refunded.rb +1 -1
  60. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -2
  61. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +3 -1
  62. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +3 -1
  63. data/lib/pay/stripe/webhooks/customer_deleted.rb +7 -15
  64. data/lib/pay/stripe/webhooks/customer_updated.rb +10 -3
  65. data/lib/pay/stripe/webhooks/payment_action_required.rb +2 -2
  66. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +6 -8
  67. data/lib/pay/stripe/webhooks/payment_method_attached.rb +15 -0
  68. data/lib/pay/stripe/webhooks/payment_method_detached.rb +12 -0
  69. data/lib/pay/stripe/webhooks/payment_method_updated.rb +10 -4
  70. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
  71. data/lib/pay/stripe/webhooks/subscription_deleted.rb +2 -1
  72. data/lib/pay/stripe/webhooks/subscription_renewing.rb +12 -2
  73. data/lib/pay/stripe.rb +6 -3
  74. data/lib/pay/version.rb +1 -1
  75. data/lib/pay/webhooks/delegator.rb +4 -0
  76. data/lib/pay/webhooks/process_job.rb +9 -0
  77. data/lib/pay/webhooks.rb +1 -0
  78. data/lib/pay.rb +7 -78
  79. data/lib/tasks/pay.rake +20 -0
  80. metadata +23 -36
  81. data/app/models/pay.rb +0 -5
  82. data/db/migrate/20170205020145_create_pay_subscriptions.rb +0 -17
  83. data/db/migrate/20170727235816_create_pay_charges.rb +0 -18
  84. data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +0 -14
  85. data/db/migrate/20200603134434_add_data_to_pay_models.rb +0 -6
  86. data/db/migrate/20210309004259_add_data_to_pay_billable.rb +0 -10
  87. data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +0 -5
  88. data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +0 -7
  89. data/lib/pay/billable/sync_email.rb +0 -40
  90. data/lib/pay/billable.rb +0 -172
@@ -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
@@ -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
@@ -1,17 +1,17 @@
1
1
  module Pay
2
2
  module Paddle
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
@@ -19,17 +19,23 @@ module Pay
19
19
  end
20
20
 
21
21
  def charge(amount, options = {})
22
- subscription = billable.subscription
22
+ subscription = pay_customer.subscription
23
23
  return unless subscription.processor_id
24
24
  raise Pay::Error, "A charge_name is required to create a one-time charge" if options[:charge_name].nil?
25
+
25
26
  response = PaddlePay::Subscription::Charge.create(subscription.processor_id, amount.to_f / 100, options[:charge_name], options)
26
- charge = billable.charges.find_or_initialize_by(processor: :paddle, processor_id: response[:invoice_id])
27
- charge.update(
28
- amount: Integer(response[:amount].to_f * 100),
29
- card_type: processor_subscription(subscription.processor_id).payment_information[:payment_method],
27
+
28
+ attributes = {
29
+ amount: (response[:amount].to_f * 100).to_i,
30
30
  paddle_receipt_url: response[:receipt_url],
31
31
  created_at: Time.zone.parse(response[:payment_date])
32
- )
32
+ }
33
+
34
+ # Lookup subscription payment method details
35
+ attributes.merge! Pay::Paddle::PaymentMethod.payment_method_details_for(subscription_id: subscription.processor_id)
36
+
37
+ charge = pay_customer.charges.find_or_initialize_by(processor_id: response[:invoice_id])
38
+ charge.update(attributes)
33
39
  charge
34
40
  rescue ::PaddlePay::PaddlePayError => e
35
41
  raise Pay::Paddle::Error, e
@@ -39,8 +45,8 @@ module Pay
39
45
  # pass
40
46
  end
41
47
 
42
- def update_card(token)
43
- sync_payment_information_from_paddle
48
+ def add_payment_method(token, default: true)
49
+ Pay::Paddle::PaymentMethod.sync(self)
44
50
  end
45
51
 
46
52
  def update_email!
@@ -58,42 +64,6 @@ module Pay
58
64
  rescue ::PaddlePay::PaddlePayError => e
59
65
  raise Pay::Paddle::Error, e
60
66
  end
61
-
62
- def invoice!(options = {})
63
- # pass
64
- end
65
-
66
- def upcoming_invoice
67
- # pass
68
- end
69
-
70
- def sync_payment_information
71
- billable.update!(payment_information(billable.subscription.processor_id))
72
- rescue ::PaddlePay::PaddlePayError => e
73
- raise Pay::Paddle::Error, e
74
- end
75
-
76
- def payment_information(subscription_id)
77
- subscription_user = PaddlePay::Subscription::User.list({subscription_id: subscription_id}).try(:first)
78
- payment_information = subscription_user ? subscription_user[:payment_information] : nil
79
- return {} if payment_information.nil?
80
-
81
- case payment_information[:payment_method]
82
- when "card"
83
- {
84
- card_type: payment_information[:card_type],
85
- card_last4: payment_information[:last_four_digits],
86
- card_exp_month: payment_information[:expiry_date].split("/").first,
87
- card_exp_year: payment_information[:expiry_date].split("/").last
88
- }
89
- when "paypal"
90
- {
91
- card_type: "PayPal"
92
- }
93
- else
94
- {}
95
- end
96
- end
97
67
  end
98
68
  end
99
69
  end
@@ -3,15 +3,15 @@ module Pay
3
3
  class Charge
4
4
  attr_reader :pay_charge
5
5
 
6
- delegate :processor_id, :owner, to: :pay_charge
6
+ delegate :processor_id, :customer, to: :pay_charge
7
7
 
8
8
  def initialize(pay_charge)
9
9
  @pay_charge = pay_charge
10
10
  end
11
11
 
12
12
  def charge
13
- return unless owner.subscription
14
- payments = PaddlePay::Subscription::Payment.list({subscription_id: owner.subscription.processor_id})
13
+ return unless customer.subscription
14
+ payments = PaddlePay::Subscription::Payment.list({subscription_id: customer.subscription.processor_id})
15
15
  charges = payments.select { |p| p[:id].to_s == processor_id }
16
16
  charges.try(:first)
17
17
  rescue ::PaddlePay::PaddlePayError => e
@@ -19,8 +19,8 @@ module Pay
19
19
  end
20
20
 
21
21
  def refund!(amount_to_refund)
22
- return unless owner.subscription
23
- payments = PaddlePay::Subscription::Payment.list({subscription_id: owner.subscription.processor_id, is_paid: 1})
22
+ return unless customer.subscription
23
+ payments = PaddlePay::Subscription::Payment.list({subscription_id: customer.subscription.processor_id, is_paid: 1})
24
24
  if payments.count > 0
25
25
  PaddlePay::Subscription::Payment.refund(payments.last[:id], {amount: amount_to_refund})
26
26
  pay_charge.update(amount_refunded: amount_to_refund)