pay 2.6.10 → 2.7.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -54
  3. data/app/models/pay/application_record.rb +1 -0
  4. data/app/models/pay/charge.rb +3 -1
  5. data/app/models/pay/subscription.rb +5 -3
  6. data/db/migrate/20200603134434_add_data_to_pay_models.rb +2 -18
  7. data/db/migrate/20210309004259_add_data_to_pay_billable.rb +10 -0
  8. data/db/migrate/20210406215234_add_currency_to_pay_charges.rb +5 -0
  9. data/db/migrate/20210406215506_add_application_fee_to_pay_models.rb +7 -0
  10. data/db/migrate/20210714175351_add_uniqueness_to_pay_models.rb +6 -0
  11. data/lib/generators/active_record/billable_generator.rb +44 -0
  12. data/lib/generators/active_record/merchant_generator.rb +44 -0
  13. data/lib/generators/active_record/templates/billable_migration.rb +17 -0
  14. data/lib/generators/active_record/templates/merchant_migration.rb +12 -0
  15. data/lib/generators/pay/{pay_generator.rb → billable_generator.rb} +2 -3
  16. data/lib/generators/pay/merchant_generator.rb +17 -0
  17. data/lib/generators/pay/orm_helpers.rb +10 -6
  18. data/lib/pay.rb +22 -0
  19. data/lib/pay/adapter.rb +22 -0
  20. data/lib/pay/billable.rb +4 -0
  21. data/lib/pay/braintree/billable.rb +8 -1
  22. data/lib/pay/braintree/subscription.rb +6 -0
  23. data/lib/pay/env.rb +8 -0
  24. data/lib/pay/fake_processor/subscription.rb +6 -0
  25. data/lib/pay/merchant.rb +37 -0
  26. data/lib/pay/paddle/subscription.rb +9 -0
  27. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +3 -1
  28. data/lib/pay/stripe.rb +11 -0
  29. data/lib/pay/stripe/billable.rb +39 -36
  30. data/lib/pay/stripe/charge.rb +62 -4
  31. data/lib/pay/stripe/merchant.rb +66 -0
  32. data/lib/pay/stripe/subscription.rb +87 -21
  33. data/lib/pay/stripe/webhooks/account_updated.rb +17 -0
  34. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
  35. data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
  36. data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +13 -0
  37. data/lib/pay/stripe/webhooks/checkout_session_completed.rb +13 -0
  38. data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +2 -8
  39. data/lib/pay/stripe/webhooks/payment_method_attached.rb +17 -0
  40. data/lib/pay/stripe/webhooks/payment_method_automatically_updated.rb +17 -0
  41. data/lib/pay/stripe/webhooks/payment_method_detached.rb +17 -0
  42. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -35
  43. data/lib/pay/stripe/webhooks/subscription_deleted.rb +1 -9
  44. data/lib/pay/stripe/webhooks/subscription_renewing.rb +4 -6
  45. data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -28
  46. data/lib/pay/version.rb +1 -1
  47. metadata +22 -6
  48. data/lib/generators/active_record/pay_generator.rb +0 -58
  49. data/lib/generators/active_record/templates/migration.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd1db7767f660b6f1b0e533e71d363fce74f394b3e0217350290b9588b30b7e5
4
- data.tar.gz: 6466150e085d5e0b415d6228b06663e2e05e6b1090fa57fc1b6000498b48431d
3
+ metadata.gz: 2cb954b3125acb413cdebfcf57687b4ff9f33987c0500dd72f350c293f491322
4
+ data.tar.gz: b4f9b23d452a3ab76b21902ebcad03b2c927a7e7eb17f58c9ac06d3df22232a3
5
5
  SHA512:
6
- metadata.gz: 45d7619103bc84a79b9704be7f788cec65235d2630be9bc2afa2f78802972c1e229a26b0d5edb91f20d1f28a6358460cb3a2cae0e5a7c38abe189f7f1114bec2
7
- data.tar.gz: b6e3a7aa9b6c3292da2d43fad800d6c7f58b655c4804a3560ef4de4e6d3a3df2827f54007c7ab64acafa98c81c404b85051c2718daccb7039b3d860eb01e2dda
6
+ metadata.gz: 60d31163be2531db82eb4eb1cd6218f83e3b88d4a2a18821f4f88f19465f9e6bf2c7215f4a3ac00348582f98b3122f3820a54bdb6d19e338d6d37e68b14e0803
7
+ data.tar.gz: 6cc782accfeea3325fb93c6c15d3044efb08de166a07c90f868fc202cbc99d6fd9eda5d07db8cb97f14edff4c1d257be6261d408deafc8d967e399b6107e1cf5
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Pay - Payments engine for Ruby on Rails
4
4
 
5
- [![Build Status](https://github.com/pay-rails/pay/workflows/Tests/badge.svg)](https://github.com/pay-rails/pay/actions) [![Gem Version](https://badge.fury.io/rb/pay.svg)](https://badge.fury.io/rb/pay)
5
+ [![Build Status](https://github.com/pay-rails/pay/workflows/Tests/badge.svg)](https://github.com/pay-rails/pay/actions) [![Gem Version](https://badge.fury.io/rb/pay.svg)](https://badge.fury.io/rb/pay)
6
6
 
7
7
  <img src="docs/images/stripe_partner_badge.svg" height="26px">
8
8
 
@@ -51,58 +51,49 @@ And then execute:
51
51
  bundle
52
52
  ```
53
53
 
54
- Make sure you've configured your ActionMailer default_url_options so Pay can generate links to for features like Stripe Checkout.
54
+ Next, we need to add migrations to your application, run the following migration:
55
55
 
56
- ```ruby
57
- # config/application.rb
58
- config.action_mailer.default_url_options = { host: "example.com" }
59
- ```
56
+ ````bash
57
+ bin/rails pay:install:migrations
58
+ ````
60
59
 
61
- #### Migrations
62
-
63
- To add the migrations to your application, run the following migration:
64
-
65
- `bin/rails pay:install:migrations`
60
+ >If your models rely on non integer ids (uuids for example) you will need to alter the `create_pay_subscriptions` and `create_pay_charges` migrations.
66
61
 
67
62
  We also need to run migrations to add Pay to the User, Account, Team, etc models that we want to make payments in our app.
68
63
 
69
- `bin/rails g pay User`
64
+ ```bash
65
+ bin/rails g pay:billable User
66
+ ```
70
67
 
71
68
  This will generate a migration to add Pay fields to our User model and automatically includes the `Pay::Billable` module in our `User` model. Repeat this for all the models you want to make payments in your app.
72
69
 
73
- Finally, run the migrations
74
-
75
- `rake db:migrate`
70
+ **Note:** An `email` attribute or method on your `Billable` model is required.
76
71
 
77
- #### Getting NoMethodError?
72
+ To sync customer names, your `Billable` model should respond to the `first_name` and `last_name` methods. Pay will sync these over to your Customer objects in Stripe and Braintree.
78
73
 
79
- `NoMethodError (undefined method 'stripe_customer' for #<User:0x00007fbc34b9bf20>)`
74
+ Finally, run the migrations
80
75
 
81
- Fully restart your Rails application `bin/spring stop && rails s`
76
+ ```bash
77
+ bin/rails db:migrate
78
+ ```
82
79
 
83
- ## Usage
80
+ > If you run into `NoMethodError (undefined method 'stripe_customer' for #<User:0x00007fbc34b9bf20>)`, fully restart your Rails application `bin/spring stop && rails s`
84
81
 
85
- The `Pay::Billable` module should be included in the models you want to make payments and subscriptions.
82
+ Lastly, make sure you've configured your ActionMailer default_url_options so Pay can generate links to for features like Stripe Checkout.
86
83
 
87
84
  ```ruby
88
- # app/models/user.rb
89
- class User < ActiveRecord::Base
90
- include Pay::Billable
91
- end
85
+ # config/application.rb
86
+ config.action_mailer.default_url_options = { host: "example.com" }
92
87
  ```
93
88
 
94
- An `email` attribute or method on your `Billable` model is required.
95
-
96
- To sync over customer names, your `Billable` model should respond to the `first_name` and `last_name` methods. Pay will sync these over to your Customer objects in Stripe and Braintree.
97
-
98
89
  ## Configuration
99
90
 
100
91
  Need to make some changes to how Pay is used? You can create an initializer `config/initializers/pay.rb`
101
92
 
102
93
  ```ruby
103
94
  Pay.setup do |config|
104
- config.chargeable_class = 'Pay::Charge'
105
- config.chargeable_table = 'pay_charges'
95
+ # config.chargeable_class = 'Pay::Charge'
96
+ # config.chargeable_table = 'pay_charges'
106
97
 
107
98
  # For use in the receipt/refund/renewal mailers
108
99
  config.business_name = "Business Name"
@@ -136,29 +127,63 @@ end
136
127
 
137
128
  ### Credentials
138
129
 
139
- You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
130
+ Pay automatically looks up credentials for each payment provider. We recommend storing them in the Rails credentials.
131
+
132
+ ##### Rails Credentials & Secrets
133
+
134
+ You'll need to add your API keys to your Rails credentials. You can do this by running:
135
+
136
+ ```bash
137
+ bin/rails credentials:edit --environment=development
138
+ ```
139
+
140
+ They should be formatted like the following:
141
+
142
+ ```yaml
143
+ stripe:
144
+ private_key: sk_test_xxxx
145
+ public_key: pk_test_yyyy
146
+ signing_secret: whsec_zzzz
147
+
148
+ braintree:
149
+ private_key: xxxx
150
+ public_key: yyyy
151
+ merchant_id: aaaa
152
+ environment: sandbox
153
+
154
+ paddle:
155
+ vendor_id: xxxx
156
+ vendor_auth_code: yyyy
157
+ public_key_base64: MII...==
158
+ environment: sandbox
159
+ ```
160
+
161
+ You can also nest these credentials under the Rails environment if using a shared credentials file or secrets.
140
162
 
141
163
  ```yaml
142
164
  development:
143
165
  stripe:
144
- private_key: xxxx
145
- public_key: yyyy
146
- signing_secret: zzzz
147
- braintree:
148
- private_key: xxxx
149
- public_key: yyyy
150
- merchant_id: aaaa
151
- environment: sandbox
152
- paddle:
153
- vendor_id: xxxx
154
- vendor_auth_code: yyyy
155
- public_key_base64: MII...==
156
- environment: sandbox
166
+ private_key: sk_test_xxxx
167
+ public_key: pk_test_yyyy
168
+ signing_secret: whsec_zzzz
169
+ # ...
157
170
  ```
158
171
 
159
- For Stripe, you can also use the `STRIPE_PUBLIC_KEY`, `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
160
- For Braintree, you can also use `BRAINTREE_MERCHANT_ID`, `BRAINTREE_PUBLIC_KEY`, `BRAINTREE_PRIVATE_KEY`, and `BRAINTREE_ENVIRONMENT` environment variables.
161
- For Paddle, you can also use `PADDLE_VENDOR_ID`, `PADDLE_VENDOR_AUTH_CODE`, `PADDLE_PUBLIC_KEY_BASE64` and `PADDLE_ENVIRONMENT` environment variables.
172
+ ##### Environment Variables
173
+
174
+ Pay will also check environment variables for API keys:
175
+
176
+ * `STRIPE_PUBLIC_KEY`
177
+ * `STRIPE_PRIVATE_KEY`
178
+ * `STRIPE_SIGNING_SECRET`
179
+ * `BRAINTREE_MERCHANT_ID`
180
+ * `BRAINTREE_PUBLIC_KEY`
181
+ * `BRAINTREE_PRIVATE_KEY`
182
+ * `BRAINTREE_ENVIRONMENT`
183
+ * `PADDLE_VENDOR_ID`
184
+ * `PADDLE_VENDOR_AUTH_CODE`
185
+ * `PADDLE_PUBLIC_KEY_BASE64`
186
+ * `PADDLE_ENVIRONMENT`
162
187
 
163
188
  ### Generators
164
189
 
@@ -176,11 +201,13 @@ bin/rails generate pay:email_views
176
201
 
177
202
  ### Emails
178
203
 
179
- Emails can be enabled/disabled using the `send_emails` configuration option (enabled per default). When enabled, the following emails will be sent:
204
+ Emails can be enabled/disabled using the `send_emails` configuration option (enabled by default).
180
205
 
181
- - When a charge succeeded
182
- - When a charge was refunded
183
- - When a subscription is about to renew
206
+ When enabled, the following emails will be sent when:
207
+
208
+ - A charge succeeded
209
+ - A charge was refunded
210
+ - A subscription is about to renew
184
211
 
185
212
 
186
213
  ## Billable API
@@ -585,10 +612,18 @@ If you have a catch all route (for 404s etc) and need to control where/when the
585
612
 
586
613
  ```ruby
587
614
  # config/initializers/pay.rb
588
- config.automount_routes = false
615
+ Pay.setup do |config|
616
+ # ...
617
+
618
+ config.automount_routes = false
619
+ end
589
620
 
590
621
  # config/routes.rb
591
- mount Pay::Engine, at: '/secret-webhook-path'
622
+ Rails.application.routes.draw do
623
+ mount Pay::Engine, at: '/pay' # You can change the `at` path to feed your needs.
624
+
625
+ # Other routes here
626
+ end
592
627
  ```
593
628
 
594
629
  If you just want to modify where the engine mounts it's routes then you can change the path.
@@ -596,7 +631,11 @@ If you just want to modify where the engine mounts it's routes then you can chan
596
631
  ```ruby
597
632
  # config/initializers/pay.rb
598
633
 
599
- config.routes_path = '/secret-webhook-path'
634
+ Pay.setup do |config|
635
+ # ...
636
+
637
+ config.routes_path = '/pay'
638
+ end
600
639
  ```
601
640
 
602
641
  ## Payment Providers
@@ -1,6 +1,7 @@
1
1
  module Pay
2
2
  class ApplicationRecord < Pay.model_parent_class.constantize
3
3
  self.abstract_class = true
4
+ self.table_name_prefix = "pay_"
4
5
 
5
6
  def self.json_column?(name)
6
7
  return unless connected? && table_exists?
@@ -7,6 +7,7 @@ module Pay
7
7
 
8
8
  # Associations
9
9
  belongs_to :owner, polymorphic: true
10
+ belongs_to :subscription, optional: true, class_name: "Pay::Subscription", foreign_key: :pay_subscription_id
10
11
 
11
12
  # Scopes
12
13
  scope :sorted, -> { order(created_at: :desc) }
@@ -15,10 +16,11 @@ module Pay
15
16
  # Validations
16
17
  validates :amount, presence: true
17
18
  validates :processor, presence: true
18
- validates :processor_id, presence: true
19
+ validates :processor_id, presence: true, uniqueness: {scope: :processor, case_sensitive: false}
19
20
  validates :card_type, presence: true
20
21
 
21
22
  store_accessor :data, :paddle_receipt_url
23
+ store_accessor :data, :stripe_account
22
24
 
23
25
  # Helpers for payment processors
24
26
  %w[braintree stripe paddle fake_processor].each do |processor_name|
@@ -9,11 +9,12 @@ module Pay
9
9
 
10
10
  # Associations
11
11
  belongs_to :owner, polymorphic: true
12
+ has_many :charges, class_name: "Pay::Charge", foreign_key: :pay_subscription_id
12
13
 
13
14
  # Validations
14
15
  validates :name, presence: true
15
16
  validates :processor, presence: true
16
- validates :processor_id, presence: true
17
+ validates :processor_id, presence: true, uniqueness: {scope: :processor, case_sensitive: false}
17
18
  validates :processor_plan, presence: true
18
19
  validates :quantity, presence: true
19
20
  validates :status, presence: true
@@ -31,6 +32,7 @@ module Pay
31
32
  store_accessor :data, :paddle_update_url
32
33
  store_accessor :data, :paddle_cancel_url
33
34
  store_accessor :data, :paddle_paused_from
35
+ store_accessor :data, :stripe_account
34
36
 
35
37
  attribute :prorate, :boolean, default: true
36
38
 
@@ -115,8 +117,8 @@ module Pay
115
117
  owner.invoice!(subscription_id: processor_id)
116
118
  end
117
119
 
118
- def processor_subscription(options = {})
119
- owner.processor_subscription(processor_id, options)
120
+ def processor_subscription(**options)
121
+ payment_processor.subscription(**options)
120
122
  end
121
123
 
122
124
  def latest_payment
@@ -1,22 +1,6 @@
1
1
  class AddDataToPayModels < ActiveRecord::Migration[4.2]
2
2
  def change
3
- add_column :pay_subscriptions, :data, data_column_type
4
- add_column :pay_charges, :data, data_column_type
5
- end
6
-
7
- def data_column_type
8
- default_hash = ActiveRecord::Base.configurations.default_hash
9
-
10
- # Rails 6.1 uses a symbol key instead of a string
11
- adapter = default_hash.dig(:adapter) || default_hash.dig("adapter")
12
-
13
- case adapter
14
- when "mysql2"
15
- :json
16
- when "postgresql"
17
- :jsonb
18
- else
19
- :text
20
- end
3
+ add_column :pay_subscriptions, :data, Pay::Adapter.json_column_type
4
+ add_column :pay_charges, :data, Pay::Adapter.json_column_type
21
5
  end
22
6
  end
@@ -0,0 +1,10 @@
1
+ class AddDataToPayBillable < ActiveRecord::Migration[4.2]
2
+ def change
3
+ # Load all the billable models
4
+ Rails.application.eager_load!
5
+
6
+ Pay.billable_models.each do |model|
7
+ add_column model.table_name, :pay_data, Pay::Adapter.json_column_type
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class AddCurrencyToPayCharges < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :pay_charges, :currency, :string
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class AddApplicationFeeToPayModels < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :pay_charges, :application_fee_amount, :integer
4
+ add_column :pay_subscriptions, :application_fee_percent, :decimal, precision: 8, scale: 2
5
+ add_column :pay_charges, :pay_subscription_id, :integer
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class AddUniquenessToPayModels < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_index :pay_charges, [:processor, :processor_id], unique: true
4
+ add_index :pay_subscriptions, [:processor, :processor_id], unique: true
5
+ end
6
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/active_record"
4
+ require "generators/pay/orm_helpers"
5
+
6
+ module ActiveRecord
7
+ module Generators
8
+ class BillableGenerator < ActiveRecord::Generators::Base
9
+ include Pay::Generators::OrmHelpers
10
+ source_root File.expand_path("../templates", __FILE__)
11
+
12
+ def copy_pay_billable_migration
13
+ if (behavior == :invoke && model_exists?) || (behavior == :revoke && migration_exists?(table_name))
14
+ migration_template "billable_migration.rb", "#{migration_path}/add_pay_billable_to_#{table_name}.rb", migration_version: migration_version
15
+ else
16
+ say "#{model_path} does not exist.", :red
17
+ say "⚠️ Make sure the #{name} model exists before running this generator."
18
+ end
19
+ end
20
+
21
+ # If the file already contains the contents, the user will receive this warning:
22
+ #
23
+ # File unchanged! The supplied flag value not found!
24
+ #
25
+ # This can be ignored as it just means the contents already exist and the file is unchanged.
26
+ # Thor will be updated to improve this message: https://github.com/rails/thor/issues/706
27
+ def inject_pay_billable_content
28
+ return unless model_exists?
29
+
30
+ content = model_contents
31
+ class_path = (namespaced? ? class_name.to_s.split("::") : [class_name])
32
+ indent_depth = class_path.size - 1
33
+ content = content.split("\n").map { |line| " " * indent_depth + line }.join("\n") << "\n"
34
+ inject_into_class(model_path, class_path.last, content)
35
+ end
36
+
37
+ private
38
+
39
+ def model_contents
40
+ " include Pay::Billable"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/active_record"
4
+ require "generators/pay/orm_helpers"
5
+
6
+ module ActiveRecord
7
+ module Generators
8
+ class MerchantGenerator < ActiveRecord::Generators::Base
9
+ include Pay::Generators::OrmHelpers
10
+ source_root File.expand_path("../templates", __FILE__)
11
+
12
+ def copy_pay_merchant_migration
13
+ if (behavior == :invoke && model_exists?) || (behavior == :revoke && migration_exists?(table_name))
14
+ migration_template "merchant_migration.rb", "#{migration_path}/add_pay_merchant_to_#{table_name}.rb", migration_version: migration_version
15
+ else
16
+ say "#{model_path} does not exist.", :red
17
+ say "⚠️ Make sure the #{name} model exists before running this generator."
18
+ end
19
+ end
20
+
21
+ # If the file already contains the contents, the user will receive this warning:
22
+ #
23
+ # File unchanged! The supplied flag value not found!
24
+ #
25
+ # This can be ignored as it just means the contents already exist and the file is unchanged.
26
+ # Thor will be updated to improve this message: https://github.com/rails/thor/issues/706
27
+ def inject_pay_merchant_content
28
+ return unless model_exists?
29
+
30
+ content = model_contents
31
+ class_path = (namespaced? ? class_name.to_s.split("::") : [class_name])
32
+ indent_depth = class_path.size - 1
33
+ content = content.split("\n").map { |line| " " * indent_depth + line }.join("\n") << "\n"
34
+ inject_into_class(model_path, class_path.last, content)
35
+ end
36
+
37
+ private
38
+
39
+ def model_contents
40
+ " include Pay::Merchant"
41
+ end
42
+ end
43
+ end
44
+ end