pay 2.7.0 → 2.7.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.
- checksums.yaml +4 -4
- data/README.md +73 -53
- data/db/migrate/20200603134434_add_data_to_pay_models.rb +2 -11
- data/db/migrate/20210309004259_add_data_to_pay_billable.rb +1 -10
- data/lib/generators/active_record/billable_generator.rb +44 -0
- data/lib/generators/active_record/merchant_generator.rb +44 -0
- data/lib/generators/active_record/templates/billable_migration.rb +16 -0
- data/lib/generators/active_record/templates/merchant_migration.rb +12 -0
- data/lib/generators/pay/{pay_generator.rb → billable_generator.rb} +2 -3
- data/lib/generators/pay/merchant_generator.rb +17 -0
- data/lib/generators/pay/orm_helpers.rb +10 -6
- data/lib/pay/adapter.rb +9 -0
- data/lib/pay/env.rb +8 -0
- data/lib/pay/stripe.rb +6 -0
- data/lib/pay/stripe/billable.rb +6 -27
- data/lib/pay/stripe/charge.rb +29 -0
- data/lib/pay/stripe/subscription.rb +29 -0
- data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -7
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +2 -8
- data/lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb +13 -0
- data/lib/pay/stripe/webhooks/checkout_session_completed.rb +13 -0
- data/lib/pay/stripe/webhooks/payment_intent_succeeded.rb +2 -8
- data/lib/pay/stripe/webhooks/subscription_created.rb +1 -36
- data/lib/pay/stripe/webhooks/subscription_deleted.rb +1 -9
- data/lib/pay/stripe/webhooks/subscription_renewing.rb +4 -6
- data/lib/pay/stripe/webhooks/subscription_updated.rb +1 -29
- data/lib/pay/version.rb +1 -1
- metadata +10 -5
- data/lib/generators/active_record/pay_generator.rb +0 -58
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a90781f9b476b2396d1ab4ea8d1a6317b46ac6289f5c4afef96dc991af035fd0
|
4
|
+
data.tar.gz: 8d20e7c6edcb13eb62d5ad398c4ab12a353e3be784c7651d795efd9feeb18402
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11f281fe1df7c9eabfef2d4a0242257001115b3f7eaaa5571d73cbdacb1810891a0a57ab24a23caa01458de399093b3c82a3c251237e9d3d13f993e670f06b04
|
7
|
+
data.tar.gz: 11d760da77af12107686c4e91fcbec0f7bf4aa91e99d41281f1510bedabc00406146ade7656477ffa5ee4dda4e7b07af55a9dd0ae7675d1e780e8f4f121d6cc5
|
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,61 +51,49 @@ And then execute:
|
|
51
51
|
bundle
|
52
52
|
```
|
53
53
|
|
54
|
-
|
54
|
+
Next, we need to add migrations to your application, run the following migration:
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
```
|
60
|
-
|
61
|
-
#### Migrations
|
62
|
-
|
63
|
-
To add the migrations to your application, run the following migration:
|
56
|
+
````bash
|
57
|
+
bin/rails pay:install:migrations
|
58
|
+
````
|
64
59
|
|
65
|
-
`
|
66
|
-
|
67
|
-
>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 to ensure the `owner_id` column's type matches the model's id's format.
|
68
|
-
> As commented in the migrations adding a type parameter to the `t.references :owner` line will ensure the `owner_id` column is of the correct type.
|
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.
|
69
61
|
|
70
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.
|
71
63
|
|
72
|
-
|
64
|
+
```bash
|
65
|
+
bin/rails g pay:billable User
|
66
|
+
```
|
73
67
|
|
74
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.
|
75
69
|
|
76
|
-
|
77
|
-
|
78
|
-
`rake db:migrate`
|
70
|
+
**Note:** An `email` attribute or method on your `Billable` model is required.
|
79
71
|
|
80
|
-
|
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.
|
81
73
|
|
82
|
-
|
74
|
+
Finally, run the migrations
|
83
75
|
|
84
|
-
|
76
|
+
```bash
|
77
|
+
rake db:migrate
|
78
|
+
```
|
85
79
|
|
86
|
-
|
80
|
+
> If you run into `NoMethodError (undefined method 'stripe_customer' for #<User:0x00007fbc34b9bf20>)`, fully restart your Rails application `bin/spring stop && rails s`
|
87
81
|
|
88
|
-
|
82
|
+
Lastly, make sure you've configured your ActionMailer default_url_options so Pay can generate links to for features like Stripe Checkout.
|
89
83
|
|
90
84
|
```ruby
|
91
|
-
#
|
92
|
-
|
93
|
-
include Pay::Billable
|
94
|
-
end
|
85
|
+
# config/application.rb
|
86
|
+
config.action_mailer.default_url_options = { host: "example.com" }
|
95
87
|
```
|
96
88
|
|
97
|
-
An `email` attribute or method on your `Billable` model is required.
|
98
|
-
|
99
|
-
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.
|
100
|
-
|
101
89
|
## Configuration
|
102
90
|
|
103
91
|
Need to make some changes to how Pay is used? You can create an initializer `config/initializers/pay.rb`
|
104
92
|
|
105
93
|
```ruby
|
106
94
|
Pay.setup do |config|
|
107
|
-
config.chargeable_class = 'Pay::Charge'
|
108
|
-
config.chargeable_table = 'pay_charges'
|
95
|
+
# config.chargeable_class = 'Pay::Charge'
|
96
|
+
# config.chargeable_table = 'pay_charges'
|
109
97
|
|
110
98
|
# For use in the receipt/refund/renewal mailers
|
111
99
|
config.business_name = "Business Name"
|
@@ -139,29 +127,59 @@ end
|
|
139
127
|
|
140
128
|
### Credentials
|
141
129
|
|
142
|
-
|
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
|
+
rails credentials:edit --environment=development
|
138
|
+
```
|
139
|
+
|
140
|
+
They should be formatted like the following:
|
141
|
+
|
142
|
+
```yaml
|
143
|
+
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
|
157
|
+
```
|
158
|
+
|
159
|
+
You can also nest these credentials under the Rails environment if using a shared credentials file or secrets.
|
143
160
|
|
144
161
|
```yaml
|
145
162
|
development:
|
146
163
|
stripe:
|
147
164
|
private_key: xxxx
|
148
|
-
|
149
|
-
signing_secret: zzzz
|
150
|
-
braintree:
|
151
|
-
private_key: xxxx
|
152
|
-
public_key: yyyy
|
153
|
-
merchant_id: aaaa
|
154
|
-
environment: sandbox
|
155
|
-
paddle:
|
156
|
-
vendor_id: xxxx
|
157
|
-
vendor_auth_code: yyyy
|
158
|
-
public_key_base64: MII...==
|
159
|
-
environment: sandbox
|
165
|
+
# ...
|
160
166
|
```
|
161
167
|
|
162
|
-
|
163
|
-
|
164
|
-
|
168
|
+
##### Environment Variables
|
169
|
+
|
170
|
+
Pay will also check environment variables for API keys:
|
171
|
+
|
172
|
+
* `STRIPE_PUBLIC_KEY`
|
173
|
+
* `STRIPE_PRIVATE_KEY`
|
174
|
+
* `STRIPE_SIGNING_SECRET`
|
175
|
+
* `BRAINTREE_MERCHANT_ID`
|
176
|
+
* `BRAINTREE_PUBLIC_KEY`
|
177
|
+
* `BRAINTREE_PRIVATE_KEY`
|
178
|
+
* `BRAINTREE_ENVIRONMENT`
|
179
|
+
* `PADDLE_VENDOR_ID`
|
180
|
+
* `PADDLE_VENDOR_AUTH_CODE`
|
181
|
+
* `PADDLE_PUBLIC_KEY_BASE64`
|
182
|
+
* `PADDLE_ENVIRONMENT`
|
165
183
|
|
166
184
|
### Generators
|
167
185
|
|
@@ -179,11 +197,13 @@ bin/rails generate pay:email_views
|
|
179
197
|
|
180
198
|
### Emails
|
181
199
|
|
182
|
-
Emails can be enabled/disabled using the `send_emails` configuration option (enabled
|
200
|
+
Emails can be enabled/disabled using the `send_emails` configuration option (enabled by default).
|
201
|
+
|
202
|
+
When enabled, the following emails will be sent when:
|
183
203
|
|
184
|
-
-
|
185
|
-
-
|
186
|
-
-
|
204
|
+
- A charge succeeded
|
205
|
+
- A charge was refunded
|
206
|
+
- A subscription is about to renew
|
187
207
|
|
188
208
|
|
189
209
|
## Billable API
|
@@ -1,15 +1,6 @@
|
|
1
1
|
class AddDataToPayModels < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
|
-
add_column :pay_subscriptions, :data,
|
4
|
-
add_column :pay_charges, :data,
|
5
|
-
end
|
6
|
-
|
7
|
-
def data_column_type
|
8
|
-
case Pay::Adapter.current_adapter
|
9
|
-
when "postgresql"
|
10
|
-
:jsonb
|
11
|
-
else
|
12
|
-
:json
|
13
|
-
end
|
3
|
+
add_column :pay_subscriptions, :data, Pay::Adapter.json_column_type
|
4
|
+
add_column :pay_charges, :data, Pay::Adapter.json_column_type
|
14
5
|
end
|
15
6
|
end
|
@@ -4,16 +4,7 @@ class AddDataToPayBillable < ActiveRecord::Migration[4.2]
|
|
4
4
|
Rails.application.eager_load!
|
5
5
|
|
6
6
|
Pay.billable_models.each do |model|
|
7
|
-
add_column model.table_name, :pay_data,
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
def data_column_type
|
12
|
-
case Pay::Adapter.current_adapter
|
13
|
-
when "postgresql"
|
14
|
-
:jsonb
|
15
|
-
else
|
16
|
-
:json
|
7
|
+
add_column model.table_name, :pay_data, Pay::Adapter.json_column_type
|
17
8
|
end
|
18
9
|
end
|
19
10
|
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
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddPayBillableTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def change
|
5
|
+
change_table :<%= table_name %>, bulk: true do |t|
|
6
|
+
t.string :processor
|
7
|
+
t.string :processor_id
|
8
|
+
t.datetime :trial_ends_at
|
9
|
+
t.string :card_type
|
10
|
+
t.string :card_last4
|
11
|
+
t.string :card_exp_month
|
12
|
+
t.string :card_exp_year
|
13
|
+
t.text :extra_billing_info
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddPayMerchantTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def change
|
5
|
+
add_column table_name, :merchant_processor, :string
|
6
|
+
|
7
|
+
# We may already have the pay_data column if a Pay::Billable object is also a Pay::Merchant
|
8
|
+
unless ActiveRecord::Base.connection.column_exists?(table_name, :pay_data)
|
9
|
+
add_column :<%= table_name %>, :pay_data, Pay::Adapter.json_column_type
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -4,13 +4,12 @@ require "rails/generators/named_base"
|
|
4
4
|
|
5
5
|
module Pay
|
6
6
|
module Generators
|
7
|
-
class
|
7
|
+
class BillableGenerator < Rails::Generators::NamedBase
|
8
8
|
include Rails::Generators::ResourceHelpers
|
9
9
|
|
10
|
-
namespace "pay"
|
11
10
|
source_root File.expand_path("../templates", __FILE__)
|
12
11
|
|
13
|
-
desc "Generates a migration to add Billable fields to a model."
|
12
|
+
desc "Generates a migration to add Pay::Billable fields to a model."
|
14
13
|
|
15
14
|
hook_for :orm
|
16
15
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
|
5
|
+
module Pay
|
6
|
+
module Generators
|
7
|
+
class MerchantGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::ResourceHelpers
|
9
|
+
|
10
|
+
source_root File.expand_path("../templates", __FILE__)
|
11
|
+
|
12
|
+
desc "Generates a migration to add Pay::Merchant fields to a model."
|
13
|
+
|
14
|
+
hook_for :orm
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -3,12 +3,6 @@
|
|
3
3
|
module Pay
|
4
4
|
module Generators
|
5
5
|
module OrmHelpers
|
6
|
-
def model_contents
|
7
|
-
<<-CONTENT
|
8
|
-
include Pay::Billable
|
9
|
-
CONTENT
|
10
|
-
end
|
11
|
-
|
12
6
|
private
|
13
7
|
|
14
8
|
def model_exists?
|
@@ -30,6 +24,16 @@ module Pay
|
|
30
24
|
def model_path
|
31
25
|
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
32
26
|
end
|
27
|
+
|
28
|
+
def migration_version
|
29
|
+
if rails5_and_up?
|
30
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def rails5_and_up?
|
35
|
+
Rails::VERSION::MAJOR >= 5
|
36
|
+
end
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
data/lib/pay/adapter.rb
CHANGED
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
|
data/lib/pay/stripe.rb
CHANGED
@@ -10,6 +10,8 @@ module Pay
|
|
10
10
|
autoload :AccountUpdated, "pay/stripe/webhooks/account_updated"
|
11
11
|
autoload :ChargeRefunded, "pay/stripe/webhooks/charge_refunded"
|
12
12
|
autoload :ChargeSucceeded, "pay/stripe/webhooks/charge_succeeded"
|
13
|
+
autoload :CheckoutSessionCompleted, "pay/stripe/webhooks/checkout_session_completed"
|
14
|
+
autoload :CheckoutSessionAsyncPaymentSucceeded, "pay/stripe/webhooks/checkout_session_async_payment_succeeded"
|
13
15
|
autoload :CustomerDeleted, "pay/stripe/webhooks/customer_deleted"
|
14
16
|
autoload :CustomerUpdated, "pay/stripe/webhooks/customer_updated"
|
15
17
|
autoload :PaymentActionRequired, "pay/stripe/webhooks/payment_action_required"
|
@@ -86,6 +88,10 @@ module Pay
|
|
86
88
|
|
87
89
|
# If an account is updated in stripe, we should update it as well
|
88
90
|
events.subscribe "stripe.account.updated", Pay::Stripe::Webhooks::AccountUpdated.new
|
91
|
+
|
92
|
+
# Handle subscriptions in Stripe Checkout Sessions
|
93
|
+
events.subscribe "stripe.checkout.session.completed", Pay::Stripe::Webhooks::CheckoutSessionCompleted.new
|
94
|
+
events.subscribe "stripe.checkout.session.async_payment_succeeded", Pay::Stripe::Webhooks::CheckoutSessionAsyncPaymentSucceeded.new
|
89
95
|
end
|
90
96
|
end
|
91
97
|
end
|
data/lib/pay/stripe/billable.rb
CHANGED
@@ -65,7 +65,8 @@ module Pay
|
|
65
65
|
Pay::Payment.new(payment_intent).validate
|
66
66
|
|
67
67
|
# Create a new charge object
|
68
|
-
|
68
|
+
charge = payment_intent.charges.first
|
69
|
+
Pay::Stripe::Charge.sync(charge.id, object: charge)
|
69
70
|
rescue ::Stripe::StripeError => e
|
70
71
|
raise Pay::Stripe::Error, e
|
71
72
|
end
|
@@ -87,8 +88,11 @@ module Pay
|
|
87
88
|
# Load the Stripe customer to verify it exists and update card if needed
|
88
89
|
opts[:customer] = customer.id
|
89
90
|
|
91
|
+
# Create subscription on Stripe
|
90
92
|
stripe_sub = ::Stripe::Subscription.create(opts, {stripe_account: stripe_account})
|
91
|
-
|
93
|
+
|
94
|
+
# Save Pay::Subscription
|
95
|
+
subscription = Pay::Stripe::Subscription.sync(stripe_sub.id, object: stripe_sub, name: name)
|
92
96
|
|
93
97
|
# No trial, card requires SCA
|
94
98
|
if subscription.incomplete?
|
@@ -168,31 +172,6 @@ module Pay
|
|
168
172
|
billable.card_token = nil
|
169
173
|
end
|
170
174
|
|
171
|
-
def save_pay_charge(object)
|
172
|
-
charge = billable.charges.find_or_initialize_by(processor: :stripe, processor_id: object.id)
|
173
|
-
|
174
|
-
attrs = {
|
175
|
-
amount: object.amount,
|
176
|
-
card_last4: object.payment_method_details.card.last4,
|
177
|
-
card_type: object.payment_method_details.card.brand,
|
178
|
-
card_exp_month: object.payment_method_details.card.exp_month,
|
179
|
-
card_exp_year: object.payment_method_details.card.exp_year,
|
180
|
-
created_at: Time.zone.at(object.created),
|
181
|
-
currency: object.currency,
|
182
|
-
stripe_account: stripe_account,
|
183
|
-
application_fee_amount: object.application_fee_amount
|
184
|
-
}
|
185
|
-
|
186
|
-
# Associate charge with subscription if we can
|
187
|
-
if object.invoice
|
188
|
-
invoice = (object.invoice.is_a?(::Stripe::Invoice) ? object.invoice : ::Stripe::Invoice.retrieve(object.invoice))
|
189
|
-
attrs[:subscription] = Pay::Subscription.find_by(processor: :stripe, processor_id: invoice.subscription)
|
190
|
-
end
|
191
|
-
|
192
|
-
charge.update(attrs)
|
193
|
-
charge
|
194
|
-
end
|
195
|
-
|
196
175
|
# https://stripe.com/docs/api/checkout/sessions/create
|
197
176
|
#
|
198
177
|
# checkout(mode: "payment")
|
data/lib/pay/stripe/charge.rb
CHANGED
@@ -5,6 +5,35 @@ module Pay
|
|
5
5
|
|
6
6
|
delegate :processor_id, :owner, :stripe_account, to: :pay_charge
|
7
7
|
|
8
|
+
def self.sync(charge_id, object: nil)
|
9
|
+
object ||= ::Stripe::Charge.retrieve(id: charge_id)
|
10
|
+
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
11
|
+
return unless owner
|
12
|
+
|
13
|
+
attrs = {
|
14
|
+
amount: object.amount,
|
15
|
+
amount_refunded: object.amount_refunded,
|
16
|
+
application_fee_amount: object.application_fee_amount,
|
17
|
+
card_exp_month: object.payment_method_details.card.exp_month,
|
18
|
+
card_exp_year: object.payment_method_details.card.exp_year,
|
19
|
+
card_last4: object.payment_method_details.card.last4,
|
20
|
+
card_type: object.payment_method_details.card.brand,
|
21
|
+
created_at: Time.at(object.created),
|
22
|
+
currency: object.currency,
|
23
|
+
stripe_account: owner.stripe_account
|
24
|
+
}
|
25
|
+
|
26
|
+
# Associate charge with subscription if we can
|
27
|
+
if object.invoice
|
28
|
+
invoice = (object.invoice.is_a?(::Stripe::Invoice) ? object.invoice : ::Stripe::Invoice.retrieve(object.invoice))
|
29
|
+
attrs[:subscription] = Pay::Subscription.find_by(processor: :stripe, processor_id: invoice.subscription)
|
30
|
+
end
|
31
|
+
|
32
|
+
pay_charge = owner.charges.find_or_initialize_by(processor: :stripe, processor_id: object.id)
|
33
|
+
pay_charge.update(attrs)
|
34
|
+
pay_charge
|
35
|
+
end
|
36
|
+
|
8
37
|
def initialize(pay_charge)
|
9
38
|
@pay_charge = pay_charge
|
10
39
|
end
|
@@ -20,6 +20,35 @@ module Pay
|
|
20
20
|
:trial_ends_at,
|
21
21
|
to: :pay_subscription
|
22
22
|
|
23
|
+
def self.sync(subscription_id, object: nil, name: Pay.default_product_name)
|
24
|
+
# Skip loading the latest subscription details from the API if we already have it
|
25
|
+
object ||= ::Stripe::Subscription.retrieve(id: subscription_id, expand: ["pending_setup_intent", "latest_invoice.payment_intent"])
|
26
|
+
|
27
|
+
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
28
|
+
return unless owner
|
29
|
+
|
30
|
+
attributes = {
|
31
|
+
application_fee_percent: object.application_fee_percent,
|
32
|
+
processor_plan: object.plan.id,
|
33
|
+
quantity: object.quantity,
|
34
|
+
name: name,
|
35
|
+
status: object.status,
|
36
|
+
stripe_account: owner.stripe_account,
|
37
|
+
trial_ends_at: (object.trial_end ? Time.at(object.trial_end) : nil)
|
38
|
+
}
|
39
|
+
|
40
|
+
# Subscriptions cancelling in the future
|
41
|
+
attributes[:ends_at] = Time.at(object.current_period_end) if object.cancel_at_period_end
|
42
|
+
|
43
|
+
# Fully cancelled subscription
|
44
|
+
attributes[:ends_at] = Time.at(object.ended_at) if object.ended_at
|
45
|
+
|
46
|
+
# Update or create the subscription
|
47
|
+
pay_subscription = owner.subscriptions.find_or_initialize_by(processor: :stripe, processor_id: object.id)
|
48
|
+
pay_subscription.update(attributes)
|
49
|
+
pay_subscription
|
50
|
+
end
|
51
|
+
|
23
52
|
def initialize(pay_subscription)
|
24
53
|
@pay_subscription = pay_subscription
|
25
54
|
end
|
@@ -3,13 +3,8 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class ChargeRefunded
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
return unless charge.present?
|
10
|
-
|
11
|
-
charge.update(amount_refunded: object.amount_refunded)
|
12
|
-
notify_user(charge.owner, charge)
|
6
|
+
pay_charge = Pay::Stripe::Charge.sync(event.data.object.id)
|
7
|
+
notify_user(pay_charge.owner, pay_charge) if pay_charge
|
13
8
|
end
|
14
9
|
|
15
10
|
def notify_user(billable, charge)
|
@@ -3,14 +3,8 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class ChargeSucceeded
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
return unless billable.present?
|
10
|
-
return if billable.charges.where(processor_id: object.id).any?
|
11
|
-
|
12
|
-
charge = Pay::Stripe::Billable.new(billable).save_pay_charge(object)
|
13
|
-
notify_user(billable, charge)
|
6
|
+
pay_charge = Pay::Stripe::Charge.sync(event.data.object.id)
|
7
|
+
notify_user(pay_charge.owner, pay_charge) if pay_charge
|
14
8
|
end
|
15
9
|
|
16
10
|
def notify_user(billable, charge)
|
@@ -4,15 +4,9 @@ module Pay
|
|
4
4
|
class PaymentIntentSucceeded
|
5
5
|
def call(event)
|
6
6
|
object = event.data.object
|
7
|
-
billable = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
8
|
-
|
9
|
-
return unless billable.present?
|
10
|
-
|
11
7
|
object.charges.data.each do |charge|
|
12
|
-
|
13
|
-
|
14
|
-
charge = Pay::Stripe::Billable.new(billable).save_pay_charge(charge)
|
15
|
-
notify_user(billable, charge)
|
8
|
+
pay_charge = Pay::Stripe::Charge.sync(charge.id)
|
9
|
+
notify_user(pay_charge.owner, pay_charge) if pay_charge
|
16
10
|
end
|
17
11
|
end
|
18
12
|
|
@@ -3,42 +3,7 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionCreated
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
|
8
|
-
# We may already have the subscription in the database, so we can update that record
|
9
|
-
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.id)
|
10
|
-
|
11
|
-
# Create the subscription in the database if we don't have it already
|
12
|
-
if subscription.nil?
|
13
|
-
# The customer should already be in the database
|
14
|
-
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
15
|
-
|
16
|
-
if owner.nil?
|
17
|
-
Rails.logger.error("[Pay] Unable to find Pay::Billable with processor: :stripe and processor_id: '#{object.customer}'. Searched these models: #{Pay.billable_models.join(", ")}")
|
18
|
-
return
|
19
|
-
end
|
20
|
-
|
21
|
-
subscription = Pay.subscription_model.new(name: Pay.default_product_name, owner: owner, processor: :stripe, processor_id: object.id)
|
22
|
-
end
|
23
|
-
|
24
|
-
subscription.application_fee_percent = object.application_fee_percent
|
25
|
-
subscription.processor_plan = object.plan.id
|
26
|
-
subscription.quantity = object.quantity
|
27
|
-
subscription.status = object.status
|
28
|
-
subscription.trial_ends_at = Time.at(object.trial_end) if object.trial_end.present?
|
29
|
-
|
30
|
-
# If user was on trial, their subscription ends at the end of the trial
|
31
|
-
subscription.ends_at = if object.cancel_at_period_end && subscription.on_trial?
|
32
|
-
subscription.trial_ends_at
|
33
|
-
|
34
|
-
# User wasn't on trial, so subscription ends at period end
|
35
|
-
elsif object.cancel_at_period_end
|
36
|
-
Time.at(object.current_period_end)
|
37
|
-
|
38
|
-
# Subscription isn't marked to cancel at period end
|
39
|
-
end
|
40
|
-
|
41
|
-
subscription.save!
|
6
|
+
Pay::Stripe::Subscription.sync(event.data.object.id)
|
42
7
|
end
|
43
8
|
end
|
44
9
|
end
|
@@ -3,15 +3,7 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionDeleted
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.id)
|
8
|
-
|
9
|
-
# We couldn't find the subscription for some reason, maybe it's from another service
|
10
|
-
return if subscription.nil?
|
11
|
-
|
12
|
-
# User canceled subscriptions have an ends_at
|
13
|
-
# Automatically canceled subscriptions need this value set
|
14
|
-
subscription.update!(ends_at: Time.at(object.ended_at)) if subscription.ends_at.blank? && object.ended_at.present?
|
6
|
+
Pay::Stripe::Subscription.sync(event.data.object.id)
|
15
7
|
end
|
16
8
|
end
|
17
9
|
end
|
@@ -5,12 +5,10 @@ module Pay
|
|
5
5
|
def call(event)
|
6
6
|
# Event is of type "invoice" see:
|
7
7
|
# https://stripe.com/docs/api/invoices/object
|
8
|
-
subscription = Pay.subscription_model.find_by(
|
9
|
-
|
10
|
-
|
11
|
-
)
|
12
|
-
date = Time.zone.at(event.data.object.next_payment_attempt)
|
13
|
-
notify_user(subscription.owner, subscription, date) if subscription.present?
|
8
|
+
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: event.data.object.subscription)
|
9
|
+
return unless subscription
|
10
|
+
|
11
|
+
notify_user(subscription.owner, subscription, Time.zone.at(event.data.object.next_payment_attempt))
|
14
12
|
end
|
15
13
|
|
16
14
|
def notify_user(billable, subscription, date)
|
@@ -3,35 +3,7 @@ module Pay
|
|
3
3
|
module Webhooks
|
4
4
|
class SubscriptionUpdated
|
5
5
|
def call(event)
|
6
|
-
|
7
|
-
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.id)
|
8
|
-
|
9
|
-
return if subscription.nil?
|
10
|
-
|
11
|
-
# Delete any subscription attempts that have expired
|
12
|
-
if object.status == "incomplete_expired"
|
13
|
-
subscription.destroy
|
14
|
-
return
|
15
|
-
end
|
16
|
-
|
17
|
-
subscription.application_fee_percent = object.application_fee_percent
|
18
|
-
subscription.processor_plan = object.plan.id
|
19
|
-
subscription.quantity = object.quantity
|
20
|
-
subscription.status = object.status
|
21
|
-
subscription.trial_ends_at = Time.at(object.trial_end) if object.trial_end.present?
|
22
|
-
|
23
|
-
# If user was on trial, their subscription ends at the end of the trial
|
24
|
-
subscription.ends_at = if object.cancel_at_period_end && subscription.on_trial?
|
25
|
-
subscription.trial_ends_at
|
26
|
-
|
27
|
-
# User wasn't on trial, so subscription ends at period end
|
28
|
-
elsif object.cancel_at_period_end
|
29
|
-
Time.at(object.current_period_end)
|
30
|
-
|
31
|
-
# Subscription isn't marked to cancel at period end
|
32
|
-
end
|
33
|
-
|
34
|
-
subscription.save!
|
6
|
+
Pay::Stripe::Subscription.sync(event.data.object.id)
|
35
7
|
end
|
36
8
|
end
|
37
9
|
end
|
data/lib/pay/version.rb
CHANGED
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.7.
|
4
|
+
version: 2.7.1
|
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: 2021-
|
12
|
+
date: 2021-06-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -145,11 +145,14 @@ files:
|
|
145
145
|
- db/migrate/20210309004259_add_data_to_pay_billable.rb
|
146
146
|
- db/migrate/20210406215234_add_currency_to_pay_charges.rb
|
147
147
|
- db/migrate/20210406215506_add_application_fee_to_pay_models.rb
|
148
|
-
- lib/generators/active_record/
|
149
|
-
- lib/generators/active_record/
|
148
|
+
- lib/generators/active_record/billable_generator.rb
|
149
|
+
- lib/generators/active_record/merchant_generator.rb
|
150
|
+
- lib/generators/active_record/templates/billable_migration.rb
|
151
|
+
- lib/generators/active_record/templates/merchant_migration.rb
|
152
|
+
- lib/generators/pay/billable_generator.rb
|
150
153
|
- lib/generators/pay/email_views_generator.rb
|
154
|
+
- lib/generators/pay/merchant_generator.rb
|
151
155
|
- lib/generators/pay/orm_helpers.rb
|
152
|
-
- lib/generators/pay/pay_generator.rb
|
153
156
|
- lib/generators/pay/views_generator.rb
|
154
157
|
- lib/pay.rb
|
155
158
|
- lib/pay/adapter.rb
|
@@ -199,6 +202,8 @@ files:
|
|
199
202
|
- lib/pay/stripe/webhooks/account_updated.rb
|
200
203
|
- lib/pay/stripe/webhooks/charge_refunded.rb
|
201
204
|
- lib/pay/stripe/webhooks/charge_succeeded.rb
|
205
|
+
- lib/pay/stripe/webhooks/checkout_session_async_payment_succeeded.rb
|
206
|
+
- lib/pay/stripe/webhooks/checkout_session_completed.rb
|
202
207
|
- lib/pay/stripe/webhooks/customer_deleted.rb
|
203
208
|
- lib/pay/stripe/webhooks/customer_updated.rb
|
204
209
|
- lib/pay/stripe/webhooks/payment_action_required.rb
|
@@ -1,58 +0,0 @@
|
|
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 PayGenerator < 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 "migration.rb", "#{migration_path}/add_pay_billable_to_#{table_name}.rb", migration_version: migration_version
|
15
|
-
end
|
16
|
-
# TODO: Throw error here that model should already exist if it doesn't
|
17
|
-
end
|
18
|
-
|
19
|
-
def inject_pay_billable_content
|
20
|
-
content = model_contents
|
21
|
-
|
22
|
-
class_path = if namespaced?
|
23
|
-
class_name.to_s.split("::")
|
24
|
-
else
|
25
|
-
[class_name]
|
26
|
-
end
|
27
|
-
|
28
|
-
indent_depth = class_path.size - 1
|
29
|
-
content = content.split("\n").map { |line| " " * indent_depth + line }.join("\n") << "\n"
|
30
|
-
|
31
|
-
inject_into_class(model_path, class_path.last, content) if model_exists?
|
32
|
-
end
|
33
|
-
|
34
|
-
def migration_data
|
35
|
-
<<RUBY
|
36
|
-
t.string :processor
|
37
|
-
t.string :processor_id
|
38
|
-
t.datetime :trial_ends_at
|
39
|
-
t.string :card_type
|
40
|
-
t.string :card_last4
|
41
|
-
t.string :card_exp_month
|
42
|
-
t.string :card_exp_year
|
43
|
-
t.text :extra_billing_info
|
44
|
-
RUBY
|
45
|
-
end
|
46
|
-
|
47
|
-
def rails5_and_up?
|
48
|
-
Rails::VERSION::MAJOR >= 5
|
49
|
-
end
|
50
|
-
|
51
|
-
def migration_version
|
52
|
-
if rails5_and_up?
|
53
|
-
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|