pay 2.0.2 → 2.1.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pay might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +108 -106
- data/Rakefile +6 -12
- data/app/controllers/pay/webhooks/braintree_controller.rb +10 -7
- data/app/jobs/pay/email_sync_job.rb +3 -3
- data/app/mailers/pay/user_mailer.rb +5 -5
- data/app/models/pay/charge.rb +1 -1
- data/app/models/pay/subscription.rb +4 -4
- data/db/migrate/20170727235816_create_pay_charges.rb +2 -2
- data/db/migrate/20190816015720_add_status_to_pay_subscriptions.rb +1 -1
- data/lib/generators/active_record/pay_generator.rb +58 -0
- data/lib/generators/active_record/templates/migration.rb +9 -0
- data/lib/generators/pay/orm_helpers.rb +36 -0
- data/lib/generators/pay/pay_generator.rb +18 -0
- data/lib/pay.rb +30 -0
- data/lib/pay/billable.rb +16 -3
- data/lib/pay/billable/sync_email.rb +1 -1
- data/lib/pay/braintree.rb +1 -1
- data/lib/pay/braintree/billable.rb +28 -18
- data/lib/pay/braintree/subscription.rb +8 -8
- data/lib/pay/env.rb +5 -5
- data/lib/pay/receipts.rb +2 -2
- data/lib/pay/stripe.rb +0 -1
- data/lib/pay/stripe/billable.rb +8 -7
- data/lib/pay/stripe/subscription.rb +1 -1
- data/lib/pay/stripe/webhooks.rb +1 -1
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +6 -9
- data/lib/pay/stripe/webhooks/customer_deleted.rb +6 -6
- data/lib/pay/stripe/webhooks/customer_updated.rb +3 -3
- data/lib/pay/stripe/webhooks/payment_method_updated.rb +3 -3
- data/lib/pay/stripe/webhooks/subscription_created.rb +6 -3
- data/lib/pay/version.rb +1 -1
- metadata +10 -21
- data/db/migrate/20170503131610_add_fields_to_billable.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 883c55bf9abc32584d98fe56c9a710e92906bebc5322b1ec9ed9dadf92fcd9dc
|
4
|
+
data.tar.gz: da1849f216e7d5e3a8365b3427a4bebf914d51e1c41e9736bc3b8df726f58ef7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab3d62146de9a533a9598e0d87970a019f8b23646a92110c3d62ef64ec8385c2158c7435c7b255648faa4740360634f59d5b3a16f95c44347e7aeedc004f9674
|
7
|
+
data.tar.gz: 598d32db66f8fd5b43cfb27766826223d4a7dd80c73204e23e6a5bb36783775f8c521d8682e4c466f059e6f71541b8c799cfed5bab1330fc4992901c06cb62cc
|
data/README.md
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
<p align="center"><img src="logo.
|
1
|
+
<p align="center"><img src="docs/logo.svg" height="50px"></p>
|
2
2
|
|
3
|
-
## Pay
|
3
|
+
## Pay - Payments engine for Ruby on Rails
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
[![Build Status](https://github.com/excid3/pay/workflows/Tests/badge.svg)](https://github.com/excid3/pay/actions)
|
5
|
+
[![Build Status](https://github.com/pay-rails/pay/workflows/Tests/badge.svg)](https://github.com/pay-rails/pay/actions)
|
8
6
|
|
9
7
|
Pay is a payments engine for Ruby on Rails 4.2 and higher.
|
10
8
|
|
@@ -15,12 +13,20 @@ Pay is a payments engine for Ruby on Rails 4.2 and higher.
|
|
15
13
|
|
16
14
|
Want to add a new payment provider? Contributions are welcome and the instructions [are here](https://github.com/jasoncharnes/pay/wiki/New-Payment-Provider).
|
17
15
|
|
16
|
+
**Check the CHANGELOG for any required migrations or changes needed if you're upgrading from a previous version of Pay.**
|
17
|
+
|
18
|
+
## Tutorial
|
19
|
+
|
20
|
+
Want to see how Pay works? Check out our video getting started guide.
|
21
|
+
|
22
|
+
<a href="https://www.youtube.com/watch?v=hYlOmqyJIgc" target="_blank"><img width="50%" src="http://i3.ytimg.com/vi/hYlOmqyJIgc/maxresdefault.jpg"></a>
|
23
|
+
|
18
24
|
## Installation
|
19
25
|
|
20
26
|
Add these lines to your application's Gemfile:
|
21
27
|
|
22
28
|
```ruby
|
23
|
-
gem 'pay'
|
29
|
+
gem 'pay', '~> 2.0'
|
24
30
|
|
25
31
|
# To use Stripe, also include:
|
26
32
|
gem 'stripe', '< 6.0', '>= 2.8'
|
@@ -30,7 +36,7 @@ gem 'stripe_event', '~> 2.3'
|
|
30
36
|
gem 'braintree', '< 3.0', '>= 2.92.0'
|
31
37
|
|
32
38
|
# To use Receipts
|
33
|
-
gem 'receipts', '~> 0.
|
39
|
+
gem 'receipts', '~> 1.0.0'
|
34
40
|
```
|
35
41
|
|
36
42
|
And then execute:
|
@@ -39,52 +45,21 @@ And then execute:
|
|
39
45
|
bundle
|
40
46
|
```
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
```bash
|
45
|
-
gem install pay
|
46
|
-
```
|
47
|
-
|
48
|
-
## Setup
|
49
|
-
|
50
|
-
### Migrations
|
51
|
-
|
52
|
-
This engine will create a subscription model and the neccessary migrations for the model you want to make "billable." The most common use case for the billable model is a User.
|
48
|
+
#### Migrations
|
53
49
|
|
54
50
|
To add the migrations to your application, run the following migration:
|
55
51
|
|
56
|
-
|
57
|
-
|
58
|
-
This will install four migrations:
|
59
|
-
|
60
|
-
- db/migrate/create_subscriptions.pay.rb
|
61
|
-
- db/migrate/add_fields_to_users.pay.rb
|
62
|
-
- db/migrate/create_charges.pay.rb
|
63
|
-
- db/migrate/add_status_to_subscriptions.pay.rb
|
52
|
+
`bin/rails pay:install:migrations`
|
64
53
|
|
65
|
-
|
54
|
+
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.
|
66
55
|
|
67
|
-
|
68
|
-
module in it. By default, we assume this is `User`.
|
56
|
+
`bin/rails g pay User`
|
69
57
|
|
70
|
-
|
71
|
-
initializer:
|
72
|
-
|
73
|
-
```ruby
|
74
|
-
Pay.setup do |config|
|
75
|
-
# Make the billable class the same name as your ActiveRecord model
|
76
|
-
config.billable_class = "Team"
|
77
|
-
|
78
|
-
# Make the billable table the same name as your ActiveRecord table name for the model
|
79
|
-
# This is optional.
|
80
|
-
# Once you update the billable class, the table name will use the ActiveRecord inflected table name
|
81
|
-
config.billable_table = "teams"
|
82
|
-
end
|
83
|
-
```
|
58
|
+
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.
|
84
59
|
|
85
|
-
|
60
|
+
Finally, run the migrations
|
86
61
|
|
87
|
-
|
62
|
+
`rake db:migrate`
|
88
63
|
|
89
64
|
#### Getting NoMethodError?
|
90
65
|
|
@@ -92,63 +67,9 @@ Finally, run the migrations with `$ rake db:migrate`
|
|
92
67
|
|
93
68
|
Fully restart your Rails application `bin/spring stop && rails s`
|
94
69
|
|
95
|
-
## Payment Providers
|
96
|
-
|
97
|
-
We support both Stripe and Braintree and make our best attempt to
|
98
|
-
standardize the two. They function differently so keep that in mind if
|
99
|
-
you plan on doing more complex payments. It would be best to stick with
|
100
|
-
a single payment provider in that case so you don't run into
|
101
|
-
discrepancies.
|
102
|
-
|
103
|
-
#### Stripe
|
104
|
-
|
105
|
-
You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
|
106
|
-
|
107
|
-
```yaml
|
108
|
-
development:
|
109
|
-
stripe:
|
110
|
-
private_key: xxxx
|
111
|
-
public_key: yyyy
|
112
|
-
signing_secret: zzzz
|
113
|
-
```
|
114
|
-
|
115
|
-
You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
|
116
|
-
|
117
|
-
**To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
|
118
|
-
|
119
|
-
##### Strong Customer Authentication (SCA)
|
120
|
-
|
121
|
-
Our Stripe integration **requires** the use of Payment Method objects to correctly support Strong Customer Authentication with Stripe. If you've previously been using card tokens, you'll need to upgrade your Javascript integration.
|
122
|
-
|
123
|
-
Subscriptions that require SCA are marked as `incomplete` by default.
|
124
|
-
Once payment is authenticated, Stripe will send a webhook updating the
|
125
|
-
status of the subscription. You'll need to use the [Stripe CLI](https://github.com/stripe/stripe-cli) to forward
|
126
|
-
webhooks to your application to make sure your subscriptions work
|
127
|
-
correctly for SCA payments.
|
128
|
-
|
129
|
-
```bash
|
130
|
-
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
|
131
|
-
```
|
132
|
-
|
133
|
-
You should use `stripe.handleCardSetup` on the client to collect card information anytime you want to save the card and charge them later (adding a card, then charging them on the next page for example). Use `stripe.handleCardPayment` if you'd like to charge the customer immediately (think checking out of a shopping cart).
|
134
|
-
|
135
|
-
**Payment Confirmations**
|
136
|
-
|
137
|
-
Sometimes you'll have a payment that requires extra authentication. In this case, Pay provides a webhook and action for handling these payments. It will automatically email the customer and provide a link with the PaymentIntent ID in the url where the customer will be asked to fill out their name and card number to confirm the payment. Once done, they'll be redirected back to your application.
|
138
|
-
|
139
|
-
If you'd like to change the views of the payment confirmation page, you can install the views using the generator and modify the template.
|
140
|
-
|
141
|
-
[<img src="https://d1jfzjx68gj8xs.cloudfront.net/items/2s3Z0J3Z3b1J1v2K2O1a/Screen%20Shot%202019-10-10%20at%2012.56.32%20PM.png?X-CloudApp-Visitor-Id=51470" alt="Stripe SCA Payment Confirmation" style="zoom: 25%;" />](https://d1jfzjx68gj8xs.cloudfront.net/items/2s3Z0J3Z3b1J1v2K2O1a/Screen%20Shot%202019-10-10%20at%2012.56.32%20PM.png)
|
142
|
-
|
143
|
-
#### Background jobs
|
144
|
-
|
145
|
-
If a user's email is updated and they have a `processor_id` set, Pay will enqueue a background job (EmailSyncJob) to sync the email with the payment processor.
|
146
|
-
|
147
|
-
It's important you set a queue_adapter for this to happen. If you don't, the code will be executed immediately upon user update. [More information here](https://guides.rubyonrails.org/v4.2/active_job_basics.html#backends)
|
148
|
-
|
149
70
|
## Usage
|
150
71
|
|
151
|
-
|
72
|
+
The `Pay::Billable` module should be included in the models you want to make payments and subscriptions.
|
152
73
|
|
153
74
|
```ruby
|
154
75
|
# app/models/user.rb
|
@@ -165,9 +86,6 @@ Need to make some changes to how Pay is used? You can create an initializer `con
|
|
165
86
|
|
166
87
|
```ruby
|
167
88
|
Pay.setup do |config|
|
168
|
-
config.billable_class = 'User'
|
169
|
-
config.billable_table = 'users'
|
170
|
-
|
171
89
|
config.chargeable_class = 'Pay::Charge'
|
172
90
|
config.chargeable_table = 'pay_charges'
|
173
91
|
|
@@ -179,8 +97,8 @@ Pay.setup do |config|
|
|
179
97
|
|
180
98
|
config.send_emails = true
|
181
99
|
|
182
|
-
config.
|
183
|
-
config.routes_path = "/pay" # Only when
|
100
|
+
config.automount_routes = true
|
101
|
+
config.routes_path = "/pay" # Only when automount_routes is true
|
184
102
|
end
|
185
103
|
```
|
186
104
|
|
@@ -211,9 +129,12 @@ development:
|
|
211
129
|
braintree:
|
212
130
|
private_key: xxxx
|
213
131
|
public_key: yyyy
|
132
|
+
merchant_id: aaaa
|
133
|
+
environment: sandbox
|
214
134
|
```
|
215
135
|
|
216
|
-
|
136
|
+
For Stripe, you can also use the `STRIPE_PUBLIC_KEY`, `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
|
137
|
+
For Braintree, you can also use `BRAINTREE_MERCHANT_ID`, `BRAINTREE_PUBLIC_KEY`, `BRAINTREE_PRIVATE_KEY`, and `BRAINTREE_ENVIRONMENT` environment variables.
|
217
138
|
|
218
139
|
### Generators
|
219
140
|
|
@@ -237,6 +158,7 @@ Emails can be enabled/disabled using the `send_emails` configuration option (ena
|
|
237
158
|
- When a charge was refunded
|
238
159
|
- When a subscription is about to renew
|
239
160
|
|
161
|
+
|
240
162
|
## Billable API
|
241
163
|
|
242
164
|
#### Trials
|
@@ -292,6 +214,9 @@ You may pass optional arguments that will be directly passed on to
|
|
292
214
|
either Stripe or Braintree. You can use these options to charge
|
293
215
|
different currencies, etc.
|
294
216
|
|
217
|
+
On failure, a `Pay::Error` will be raised with details about the payment
|
218
|
+
failure.
|
219
|
+
|
295
220
|
#### Creating a Subscription
|
296
221
|
|
297
222
|
```ruby
|
@@ -312,6 +237,13 @@ def subscribe(name: 'default', plan: 'default', **options)
|
|
312
237
|
end
|
313
238
|
```
|
314
239
|
|
240
|
+
For example, you can pass the `quantity` option to subscribe to a plan with for per-seat pricing.
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
|
244
|
+
user.subscribe(name: "default", plan: "default", quantity: 3)
|
245
|
+
```
|
246
|
+
|
315
247
|
##### Name
|
316
248
|
|
317
249
|
Name is an internally used name for the subscription.
|
@@ -537,6 +469,76 @@ If you just want to modify where the engine mounts it's routes then you can chan
|
|
537
469
|
config.routes_path = '/secret-webhook-path'
|
538
470
|
```
|
539
471
|
|
472
|
+
## Payment Providers
|
473
|
+
|
474
|
+
We support both Stripe and Braintree and make our best attempt to
|
475
|
+
standardize the two. They function differently so keep that in mind if
|
476
|
+
you plan on doing more complex payments. It would be best to stick with
|
477
|
+
a single payment provider in that case so you don't run into
|
478
|
+
discrepancies.
|
479
|
+
|
480
|
+
#### Braintree
|
481
|
+
|
482
|
+
```yaml
|
483
|
+
development:
|
484
|
+
braintree:
|
485
|
+
private_key: xxxx
|
486
|
+
public_key: yyyy
|
487
|
+
merchant_id: zzzz
|
488
|
+
environment: sandbox
|
489
|
+
```
|
490
|
+
|
491
|
+
#### Stripe
|
492
|
+
|
493
|
+
You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
|
494
|
+
|
495
|
+
```yaml
|
496
|
+
development:
|
497
|
+
stripe:
|
498
|
+
private_key: xxxx
|
499
|
+
public_key: yyyy
|
500
|
+
signing_secret: zzzz
|
501
|
+
```
|
502
|
+
|
503
|
+
You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
|
504
|
+
|
505
|
+
**To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
|
506
|
+
|
507
|
+
##### Strong Customer Authentication (SCA)
|
508
|
+
|
509
|
+
Our Stripe integration **requires** the use of Payment Method objects to correctly support Strong Customer Authentication with Stripe. If you've previously been using card tokens, you'll need to upgrade your Javascript integration.
|
510
|
+
|
511
|
+
Subscriptions that require SCA are marked as `incomplete` by default.
|
512
|
+
Once payment is authenticated, Stripe will send a webhook updating the
|
513
|
+
status of the subscription. You'll need to use the [Stripe CLI](https://github.com/stripe/stripe-cli) to forward
|
514
|
+
webhooks to your application to make sure your subscriptions work
|
515
|
+
correctly for SCA payments.
|
516
|
+
|
517
|
+
```bash
|
518
|
+
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
|
519
|
+
```
|
520
|
+
|
521
|
+
You should use `stripe.handleCardSetup` on the client to collect card information anytime you want to save the card and charge them later (adding a card, then charging them on the next page for example). Use `stripe.handleCardPayment` if you'd like to charge the customer immediately (think checking out of a shopping cart).
|
522
|
+
|
523
|
+
The Javascript will now need to use createPaymentMethod instead of createToken. https://stripe.com/docs/js/payment_intents/create_payment_method
|
524
|
+
|
525
|
+
The Javascript also needs to have a PaymentIntent or SetupIntent created server-side and the ID passed into the Javascript to do this. That way it knows how to safely handle the card tokenization if it meets the SCA requirements.
|
526
|
+
|
527
|
+
**Payment Confirmations**
|
528
|
+
|
529
|
+
Sometimes you'll have a payment that requires extra authentication. In this case, Pay provides a webhook and action for handling these payments. It will automatically email the customer and provide a link with the PaymentIntent ID in the url where the customer will be asked to fill out their name and card number to confirm the payment. Once done, they'll be redirected back to your application.
|
530
|
+
|
531
|
+
If you'd like to change the views of the payment confirmation page, you can install the views using the generator and modify the template.
|
532
|
+
|
533
|
+
[<img src="https://d1jfzjx68gj8xs.cloudfront.net/items/2s3Z0J3Z3b1J1v2K2O1a/Screen%20Shot%202019-10-10%20at%2012.56.32%20PM.png?X-CloudApp-Visitor-Id=51470" alt="Stripe SCA Payment Confirmation" style="zoom: 25%;" />](https://d1jfzjx68gj8xs.cloudfront.net/items/2s3Z0J3Z3b1J1v2K2O1a/Screen%20Shot%202019-10-10%20at%2012.56.32%20PM.png)
|
534
|
+
|
535
|
+
#### Background jobs
|
536
|
+
|
537
|
+
If a user's email is updated and they have a `processor_id` set, Pay will enqueue a background job (EmailSyncJob) to sync the email with the payment processor.
|
538
|
+
|
539
|
+
It's important you set a queue_adapter for this to happen. If you don't, the code will be executed immediately upon user update. [More information here](https://guides.rubyonrails.org/v4.2/active_job_basics.html#backends)
|
540
|
+
|
541
|
+
|
540
542
|
## Contributors
|
541
543
|
|
542
544
|
- [Jason Charnes](https://twitter.com/jmcharnes)
|
data/Rakefile
CHANGED
@@ -14,27 +14,21 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
14
14
|
rdoc.rdoc_files.include("lib/**/*.rb")
|
15
15
|
end
|
16
16
|
|
17
|
-
APP_RAKEFILE = File.expand_path("
|
18
|
-
|
17
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
19
18
|
load "rails/tasks/engine.rake"
|
19
|
+
|
20
20
|
load "rails/tasks/statistics.rake"
|
21
21
|
|
22
|
-
|
22
|
+
unless Rails.env.test?
|
23
|
+
require "bundler/gem_tasks"
|
24
|
+
end
|
25
|
+
|
23
26
|
require "rake/testtask"
|
24
27
|
|
25
28
|
Rake::TestTask.new(:test) do |t|
|
26
|
-
t.libs << "lib"
|
27
29
|
t.libs << "test"
|
28
30
|
t.pattern = "test/**/*_test.rb"
|
29
31
|
t.verbose = false
|
30
32
|
end
|
31
33
|
|
32
34
|
task default: :test
|
33
|
-
|
34
|
-
task :console do
|
35
|
-
require "irb"
|
36
|
-
require "irb/completion"
|
37
|
-
require "pay"
|
38
|
-
ARGV.clear
|
39
|
-
IRB.start
|
40
|
-
end
|
@@ -16,7 +16,7 @@ module Pay
|
|
16
16
|
end
|
17
17
|
|
18
18
|
render json: {success: true}, status: :ok
|
19
|
-
rescue ::Braintree::InvalidSignature
|
19
|
+
rescue ::Braintree::InvalidSignature
|
20
20
|
head :ok
|
21
21
|
end
|
22
22
|
|
@@ -29,11 +29,11 @@ module Pay
|
|
29
29
|
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
30
30
|
return unless pay_subscription.present?
|
31
31
|
|
32
|
-
|
33
|
-
charge =
|
32
|
+
billable = pay_subscription.owner
|
33
|
+
charge = billable.save_braintree_transaction(subscription.transactions.first)
|
34
34
|
|
35
35
|
if Pay.send_emails
|
36
|
-
Pay::UserMailer.receipt(
|
36
|
+
Pay::UserMailer.receipt(billable, charge).deliver_later
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -41,11 +41,14 @@ module Pay
|
|
41
41
|
subscription = event.subscription
|
42
42
|
return if subscription.nil?
|
43
43
|
|
44
|
-
|
45
|
-
return unless
|
44
|
+
pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
|
45
|
+
return unless pay_subscription.present?
|
46
|
+
|
47
|
+
billable = pay_subscription.owner
|
48
|
+
return if billable.nil?
|
46
49
|
|
47
50
|
# User canceled or failed to make payments
|
48
|
-
|
51
|
+
billable.update(braintree_subscription_id: nil)
|
49
52
|
end
|
50
53
|
|
51
54
|
def subscription_trial_ended(event)
|
@@ -2,11 +2,11 @@ module Pay
|
|
2
2
|
class EmailSyncJob < ApplicationJob
|
3
3
|
queue_as :default
|
4
4
|
|
5
|
-
def perform(id)
|
6
|
-
billable =
|
5
|
+
def perform(id, class_name)
|
6
|
+
billable = class_name.constantize.find(id)
|
7
7
|
billable.sync_email_with_processor
|
8
8
|
rescue ActiveRecord::RecordNotFound
|
9
|
-
Rails.logger.info "Couldn't find a #{
|
9
|
+
Rails.logger.info "Couldn't find a #{class_name} with ID = #{id}"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -9,7 +9,7 @@ module Pay
|
|
9
9
|
|
10
10
|
mail(
|
11
11
|
to: to(user),
|
12
|
-
subject: Pay.email_receipt_subject
|
12
|
+
subject: Pay.email_receipt_subject
|
13
13
|
)
|
14
14
|
end
|
15
15
|
|
@@ -18,7 +18,7 @@ module Pay
|
|
18
18
|
|
19
19
|
mail(
|
20
20
|
to: to(user),
|
21
|
-
subject: Pay.email_refund_subject
|
21
|
+
subject: Pay.email_refund_subject
|
22
22
|
)
|
23
23
|
end
|
24
24
|
|
@@ -27,7 +27,7 @@ module Pay
|
|
27
27
|
|
28
28
|
mail(
|
29
29
|
to: to(user),
|
30
|
-
subject: Pay.email_renewing_subject
|
30
|
+
subject: Pay.email_renewing_subject
|
31
31
|
)
|
32
32
|
end
|
33
33
|
|
@@ -44,8 +44,8 @@ module Pay
|
|
44
44
|
private
|
45
45
|
|
46
46
|
def to(user)
|
47
|
-
if user.respond_to?(:
|
48
|
-
"#{user.
|
47
|
+
if user.respond_to?(:customer_name)
|
48
|
+
"#{user.customer_name} <#{user.email}>"
|
49
49
|
else
|
50
50
|
user.email
|
51
51
|
end
|
data/app/models/pay/charge.rb
CHANGED
@@ -5,7 +5,7 @@ module Pay
|
|
5
5
|
STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid]
|
6
6
|
|
7
7
|
# Associations
|
8
|
-
belongs_to :owner,
|
8
|
+
belongs_to :owner, polymorphic: true
|
9
9
|
|
10
10
|
# Validations
|
11
11
|
validates :name, presence: true
|
@@ -17,9 +17,9 @@ module Pay
|
|
17
17
|
|
18
18
|
# Scopes
|
19
19
|
scope :for_name, ->(name) { where(name: name) }
|
20
|
-
scope :on_trial, -> { where.not(trial_ends_at: nil).where("trial_ends_at > ?", Time.zone.now) }
|
20
|
+
scope :on_trial, -> { where.not(trial_ends_at: nil).where("#{table_name}.trial_ends_at > ?", Time.zone.now) }
|
21
21
|
scope :cancelled, -> { where.not(ends_at: nil) }
|
22
|
-
scope :on_grace_period, -> { cancelled.where("ends_at > ?", Time.zone.now) }
|
22
|
+
scope :on_grace_period, -> { cancelled.where("#{table_name}.ends_at > ?", Time.zone.now) }
|
23
23
|
scope :active, -> { where(ends_at: nil).or(on_grace_period).or(on_trial) }
|
24
24
|
scope :incomplete, -> { where(status: :incomplete) }
|
25
25
|
scope :past_due, -> { where(status: :past_due) }
|
@@ -82,7 +82,7 @@ module Pay
|
|
82
82
|
|
83
83
|
send("#{processor}_resume")
|
84
84
|
|
85
|
-
update(ends_at: nil)
|
85
|
+
update(ends_at: nil, status: "active")
|
86
86
|
self
|
87
87
|
end
|
88
88
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
class CreatePayCharges < ActiveRecord::Migration[
|
1
|
+
class CreatePayCharges < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
3
|
create_table :pay_charges do |t|
|
4
|
-
t.references :owner
|
4
|
+
t.references :owner, polymorphic: true
|
5
5
|
t.string :processor, null: false
|
6
6
|
t.string :processor_id, null: false
|
7
7
|
t.integer :amount, null: false
|
@@ -0,0 +1,58 @@
|
|
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
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pay
|
4
|
+
module Generators
|
5
|
+
module OrmHelpers
|
6
|
+
def model_contents
|
7
|
+
buffer = <<-CONTENT
|
8
|
+
include Pay::Billable
|
9
|
+
CONTENT
|
10
|
+
buffer
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def model_exists?
|
16
|
+
File.exist?(File.join(destination_root, model_path))
|
17
|
+
end
|
18
|
+
|
19
|
+
def migration_exists?(table_name)
|
20
|
+
Dir.glob("#{File.join(destination_root, migration_path)}/[0-9]*_*.rb").grep(/\d+_add_devise_to_#{table_name}.rb$/).first
|
21
|
+
end
|
22
|
+
|
23
|
+
def migration_path
|
24
|
+
if Rails.version >= "5.0.3"
|
25
|
+
db_migrate_path
|
26
|
+
else
|
27
|
+
@migration_path ||= File.join("db", "migrate")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def model_path
|
32
|
+
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
|
5
|
+
module Pay
|
6
|
+
module Generators
|
7
|
+
class PayGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::ResourceHelpers
|
9
|
+
|
10
|
+
namespace "pay"
|
11
|
+
source_root File.expand_path("../templates", __FILE__)
|
12
|
+
|
13
|
+
desc "Generates a migration to add Billable fields to a model."
|
14
|
+
|
15
|
+
hook_for :orm
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/pay.rb
CHANGED
@@ -51,7 +51,23 @@ module Pay
|
|
51
51
|
yield self
|
52
52
|
end
|
53
53
|
|
54
|
+
def self.billable_models
|
55
|
+
Pay::Billable.includers
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.find_billable(processor:, processor_id:)
|
59
|
+
billable_models.each do |model|
|
60
|
+
if (record = model.find_by(processor: processor, processor_id: processor_id))
|
61
|
+
return record
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
54
68
|
def self.user_model
|
69
|
+
ActiveSupport::Deprecation.warn("Pay.user_model is deprecated and will be removed in v3. Instead, use `Pay.billable_models` now to support more than one billable model.")
|
70
|
+
|
55
71
|
if Rails.application.config.cache_classes
|
56
72
|
@@user_model ||= billable_class.constantize
|
57
73
|
else
|
@@ -86,6 +102,20 @@ module Pay
|
|
86
102
|
class Error < StandardError
|
87
103
|
end
|
88
104
|
|
105
|
+
class BraintreeError < Error
|
106
|
+
attr_reader :result
|
107
|
+
|
108
|
+
def initialize(result=nil)
|
109
|
+
@result = result
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class BraintreeAuthorizationError < BraintreeError
|
114
|
+
def message
|
115
|
+
"Either the data you submitted is malformed and does not match the API or the API key you used may not be authorized to perform this action."
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
89
119
|
class InvalidPaymentMethod < Error
|
90
120
|
attr_reader :payment
|
91
121
|
|
data/lib/pay/billable.rb
CHANGED
@@ -4,8 +4,21 @@ module Pay
|
|
4
4
|
module Billable
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
|
7
|
+
# Keep track of which Billable models we have
|
8
|
+
class << self
|
9
|
+
attr_reader :includers
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base = nil, &block)
|
13
|
+
@includers ||= []
|
14
|
+
@includers << base if base
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
included do |base|
|
8
19
|
include Pay::Billable::SyncEmail
|
20
|
+
include Pay::Stripe::Billable if defined? ::Stripe
|
21
|
+
include Pay::Braintree::Billable if defined? ::Braintree
|
9
22
|
|
10
23
|
has_many :charges, class_name: Pay.chargeable_class, foreign_key: :owner_id, inverse_of: :owner
|
11
24
|
has_many :subscriptions, class_name: Pay.subscription_class, foreign_key: :owner_id, inverse_of: :owner
|
@@ -112,7 +125,7 @@ module Pay
|
|
112
125
|
private
|
113
126
|
|
114
127
|
def check_for_processor
|
115
|
-
raise StandardError, "No payment processor selected. Make sure to set the #{
|
128
|
+
raise StandardError, "No payment processor selected. Make sure to set the #{self.class.name}'s `processor` attribute to either 'stripe' or 'braintree'." unless processor
|
116
129
|
end
|
117
130
|
|
118
131
|
# Used for creating a Pay::Subscription in the database
|
@@ -125,7 +138,7 @@ module Pay
|
|
125
138
|
processor_id: subscription.id,
|
126
139
|
processor_plan: plan,
|
127
140
|
trial_ends_at: send("#{processor}_trial_end_date", subscription),
|
128
|
-
ends_at: nil
|
141
|
+
ends_at: nil
|
129
142
|
)
|
130
143
|
subscriptions.create!(options)
|
131
144
|
end
|
@@ -32,7 +32,7 @@ module Pay
|
|
32
32
|
# Only update if the processor id is the same
|
33
33
|
# This prevents duplicate API hits if this is their first time
|
34
34
|
if processor_id? && !saved_change_to_processor_id? && saved_change_to_email?
|
35
|
-
EmailSyncJob.perform_later(id)
|
35
|
+
EmailSyncJob.perform_later(id, self.class.name)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
data/lib/pay/braintree.rb
CHANGED
@@ -19,7 +19,7 @@ module Pay
|
|
19
19
|
|
20
20
|
Pay.charge_model.include Pay::Braintree::Charge
|
21
21
|
Pay.subscription_model.include Pay::Braintree::Subscription
|
22
|
-
Pay.
|
22
|
+
Pay.billable_models.each { |model| model.include Pay::Stripe::Billable }
|
23
23
|
end
|
24
24
|
|
25
25
|
def public_key
|
@@ -12,9 +12,9 @@ module Pay
|
|
12
12
|
email: email,
|
13
13
|
first_name: try(:first_name),
|
14
14
|
last_name: try(:last_name),
|
15
|
-
payment_method_nonce: card_token
|
15
|
+
payment_method_nonce: card_token
|
16
16
|
)
|
17
|
-
raise
|
17
|
+
raise BraintreeError.new(result), result.message unless result.success?
|
18
18
|
|
19
19
|
update(processor: "braintree", processor_id: result.customer.id)
|
20
20
|
|
@@ -24,8 +24,10 @@ module Pay
|
|
24
24
|
|
25
25
|
result.customer
|
26
26
|
end
|
27
|
+
rescue ::Braintree::AuthorizationError => e
|
28
|
+
raise BraintreeAuthorizationError
|
27
29
|
rescue ::Braintree::BraintreeError => e
|
28
|
-
raise
|
30
|
+
raise BraintreeError, e.message
|
29
31
|
end
|
30
32
|
|
31
33
|
# Handles Billable#charge
|
@@ -35,13 +37,17 @@ module Pay
|
|
35
37
|
args = {
|
36
38
|
amount: amount / 100.0,
|
37
39
|
customer_id: customer.id,
|
38
|
-
options: {submit_for_settlement: true}
|
40
|
+
options: {submit_for_settlement: true}
|
39
41
|
}.merge(options)
|
40
42
|
|
41
43
|
result = gateway.transaction.sale(args)
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
raise BraintreeError.new(result), result.message unless result.success?
|
45
|
+
|
46
|
+
save_braintree_transaction(result.transaction)
|
47
|
+
rescue ::Braintree::AuthorizationError => e
|
48
|
+
raise BraintreeAuthorizationError
|
49
|
+
rescue ::Braintree::BraintreeError => e
|
50
|
+
raise BraintreeError, e.message
|
45
51
|
end
|
46
52
|
|
47
53
|
# Handles Billable#subscribe
|
@@ -62,11 +68,13 @@ module Pay
|
|
62
68
|
)
|
63
69
|
|
64
70
|
result = gateway.subscription.create(subscription_options)
|
65
|
-
raise
|
71
|
+
raise BraintreeError.new(result), result.message unless result.success?
|
66
72
|
|
67
73
|
create_subscription(result.subscription, "braintree", name, plan, status: :active)
|
74
|
+
rescue ::Braintree::AuthorizationError => e
|
75
|
+
raise BraintreeAuthorizationError
|
68
76
|
rescue ::Braintree::BraintreeError => e
|
69
|
-
raise
|
77
|
+
raise BraintreeError, e.message
|
70
78
|
end
|
71
79
|
|
72
80
|
# Handles Billable#update_card
|
@@ -78,23 +86,25 @@ module Pay
|
|
78
86
|
payment_method_nonce: token,
|
79
87
|
options: {
|
80
88
|
make_default: true,
|
81
|
-
verify_card: true
|
89
|
+
verify_card: true
|
82
90
|
}
|
83
91
|
)
|
84
|
-
raise
|
92
|
+
raise BraintreeError.new(result), result.message unless result.success?
|
85
93
|
|
86
94
|
update_braintree_card_on_file result.payment_method
|
87
95
|
update_subscriptions_to_payment_method(result.payment_method.token)
|
88
96
|
true
|
97
|
+
rescue ::Braintree::AuthorizationError => e
|
98
|
+
raise BraintreeAuthorizationError
|
89
99
|
rescue ::Braintree::BraintreeError => e
|
90
|
-
raise
|
100
|
+
raise BraintreeError, e.message
|
91
101
|
end
|
92
102
|
|
93
103
|
def update_braintree_email!
|
94
104
|
braintree_customer.update(
|
95
105
|
email: email,
|
96
106
|
first_name: try(:first_name),
|
97
|
-
last_name: try(:last_name)
|
107
|
+
last_name: try(:last_name)
|
98
108
|
)
|
99
109
|
end
|
100
110
|
|
@@ -171,7 +181,7 @@ module Pay
|
|
171
181
|
card_type: payment_method.card_type,
|
172
182
|
card_last4: payment_method.last_4,
|
173
183
|
card_exp_month: payment_method.expiration_month,
|
174
|
-
card_exp_year: payment_method.expiration_year
|
184
|
+
card_exp_year: payment_method.expiration_year
|
175
185
|
}
|
176
186
|
|
177
187
|
when "paypal_account"
|
@@ -179,7 +189,7 @@ module Pay
|
|
179
189
|
card_type: "PayPal",
|
180
190
|
card_last4: transaction.paypal_details.payer_email,
|
181
191
|
card_exp_month: nil,
|
182
|
-
card_exp_year: nil
|
192
|
+
card_exp_year: nil
|
183
193
|
}
|
184
194
|
|
185
195
|
when "android_pay_card"
|
@@ -188,7 +198,7 @@ module Pay
|
|
188
198
|
card_type: payment_method.source_card_type,
|
189
199
|
card_last4: payment_method.source_card_last_4,
|
190
200
|
card_exp_month: payment_method.expiration_month,
|
191
|
-
card_exp_year: payment_method.expiration_year
|
201
|
+
card_exp_year: payment_method.expiration_year
|
192
202
|
}
|
193
203
|
|
194
204
|
when "venmo_account"
|
@@ -196,7 +206,7 @@ module Pay
|
|
196
206
|
card_type: "Venmo",
|
197
207
|
card_last4: transaction.venmo_account_details.username,
|
198
208
|
card_exp_month: nil,
|
199
|
-
card_exp_year: nil
|
209
|
+
card_exp_year: nil
|
200
210
|
}
|
201
211
|
|
202
212
|
when "apple_pay_card"
|
@@ -205,7 +215,7 @@ module Pay
|
|
205
215
|
card_type: payment_method.card_type,
|
206
216
|
card_last4: payment_method.last_4,
|
207
217
|
card_exp_month: payment_method.expiration_month,
|
208
|
-
card_exp_year: payment_method.expiration_year
|
218
|
+
card_exp_year: payment_method.expiration_year
|
209
219
|
}
|
210
220
|
|
211
221
|
else
|
@@ -13,7 +13,7 @@ module Pay
|
|
13
13
|
update(status: :canceled, ends_at: trial_ends_at)
|
14
14
|
else
|
15
15
|
gateway.subscription.update(subscription.id, {
|
16
|
-
number_of_billing_cycles: subscription.current_billing_cycle
|
16
|
+
number_of_billing_cycles: subscription.current_billing_cycle
|
17
17
|
})
|
18
18
|
update(status: :canceled, ends_at: subscription.billing_period_end_date.to_date)
|
19
19
|
end
|
@@ -45,7 +45,7 @@ module Pay
|
|
45
45
|
|
46
46
|
gateway.subscription.update(subscription.id, {
|
47
47
|
never_expires: true,
|
48
|
-
number_of_billing_cycles: nil
|
48
|
+
number_of_billing_cycles: nil
|
49
49
|
})
|
50
50
|
end
|
51
51
|
|
@@ -80,8 +80,8 @@ module Pay
|
|
80
80
|
never_expires: true,
|
81
81
|
number_of_billing_cycles: nil,
|
82
82
|
options: {
|
83
|
-
prorate_charges: prorate
|
84
|
-
}
|
83
|
+
prorate_charges: prorate?
|
84
|
+
}
|
85
85
|
})
|
86
86
|
|
87
87
|
if result.success?
|
@@ -159,10 +159,10 @@ module Pay
|
|
159
159
|
{
|
160
160
|
inherited_from_id: "plan-credit",
|
161
161
|
amount: discount.amount,
|
162
|
-
number_of_billing_cycles: discount.number_of_billing_cycles
|
163
|
-
}
|
164
|
-
]
|
165
|
-
}
|
162
|
+
number_of_billing_cycles: discount.number_of_billing_cycles
|
163
|
+
}
|
164
|
+
]
|
165
|
+
}
|
166
166
|
}
|
167
167
|
end
|
168
168
|
|
data/lib/pay/env.rb
CHANGED
@@ -13,10 +13,10 @@ module Pay
|
|
13
13
|
# 3. Check unscoped credentials, then secrets
|
14
14
|
def find_value_by_name(scope, name)
|
15
15
|
ENV["#{scope.upcase}_#{name.upcase}"] ||
|
16
|
-
credentials
|
17
|
-
secrets
|
18
|
-
credentials
|
19
|
-
secrets
|
16
|
+
credentials&.dig(env, scope, name) ||
|
17
|
+
secrets&.dig(env, scope, name) ||
|
18
|
+
credentials&.dig(scope, name) ||
|
19
|
+
secrets&.dig(scope, name)
|
20
20
|
end
|
21
21
|
|
22
22
|
def env
|
@@ -28,7 +28,7 @@ module Pay
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def credentials
|
31
|
-
Rails.application.credentials
|
31
|
+
Rails.application.credentials if Rails.application.respond_to?(:credentials)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
data/lib/pay/receipts.rb
CHANGED
@@ -20,7 +20,7 @@ module Pay
|
|
20
20
|
company: {
|
21
21
|
name: Pay.business_name,
|
22
22
|
address: Pay.business_address,
|
23
|
-
email: Pay.support_email
|
23
|
+
email: Pay.support_email
|
24
24
|
},
|
25
25
|
line_items: line_items
|
26
26
|
)
|
@@ -32,7 +32,7 @@ module Pay
|
|
32
32
|
["Account Billed", "#{owner.name} (#{owner.email})"],
|
33
33
|
["Product", product],
|
34
34
|
["Amount", ActionController::Base.helpers.number_to_currency(amount / 100.0)],
|
35
|
-
["Charged to", charged_to]
|
35
|
+
["Charged to", charged_to]
|
36
36
|
]
|
37
37
|
line_items << ["Additional Info", owner.extra_billing_info] if owner.extra_billing_info?
|
38
38
|
line_items
|
data/lib/pay/stripe.rb
CHANGED
data/lib/pay/stripe/billable.rb
CHANGED
@@ -17,7 +17,7 @@ module Pay
|
|
17
17
|
def create_setup_intent
|
18
18
|
::Stripe::SetupIntent.create(
|
19
19
|
customer: processor_id,
|
20
|
-
usage: :off_session
|
20
|
+
usage: :off_session
|
21
21
|
)
|
22
22
|
end
|
23
23
|
|
@@ -32,7 +32,7 @@ module Pay
|
|
32
32
|
confirmation_method: :automatic,
|
33
33
|
currency: "usd",
|
34
34
|
customer: customer.id,
|
35
|
-
payment_method: customer.invoice_settings.default_payment_method
|
35
|
+
payment_method: customer.invoice_settings.default_payment_method
|
36
36
|
}.merge(options)
|
37
37
|
|
38
38
|
payment_intent = ::Stripe::PaymentIntent.create(args)
|
@@ -48,17 +48,18 @@ module Pay
|
|
48
48
|
#
|
49
49
|
# Returns Pay::Subscription
|
50
50
|
def create_stripe_subscription(name, plan, options = {})
|
51
|
+
quantity = options.delete(:quantity) || 1
|
51
52
|
opts = {
|
52
53
|
expand: ["pending_setup_intent", "latest_invoice.payment_intent"],
|
53
|
-
items: [plan: plan],
|
54
|
-
off_session: true
|
54
|
+
items: [plan: plan, quantity: quantity],
|
55
|
+
off_session: true
|
55
56
|
}.merge(options)
|
56
57
|
|
57
58
|
# Inherit trial from plan unless trial override was specified
|
58
59
|
opts[:trial_from_plan] = true unless opts[:trial_period_days]
|
59
60
|
|
60
61
|
stripe_sub = customer.subscriptions.create(opts)
|
61
|
-
subscription = create_subscription(stripe_sub, "stripe", name, plan, status: stripe_sub.status)
|
62
|
+
subscription = create_subscription(stripe_sub, "stripe", name, plan, status: stripe_sub.status, quantity: quantity)
|
62
63
|
|
63
64
|
# No trial, card requires SCA
|
64
65
|
if subscription.incomplete?
|
@@ -139,8 +140,8 @@ module Pay
|
|
139
140
|
|
140
141
|
# Update the user's card on file if a token was passed in
|
141
142
|
if card_token.present?
|
142
|
-
::Stripe::PaymentMethod.attach(card_token, {customer: customer.id})
|
143
|
-
customer.invoice_settings.default_payment_method =
|
143
|
+
payment_method = ::Stripe::PaymentMethod.attach(card_token, {customer: customer.id})
|
144
|
+
customer.invoice_settings.default_payment_method = payment_method.id
|
144
145
|
customer.save
|
145
146
|
|
146
147
|
update_stripe_card_on_file ::Stripe::PaymentMethod.retrieve(card_token).card
|
@@ -11,7 +11,7 @@ module Pay
|
|
11
11
|
subscription.save
|
12
12
|
|
13
13
|
new_ends_at = on_trial? ? trial_ends_at : Time.at(subscription.current_period_end)
|
14
|
-
update(ends_at: new_ends_at
|
14
|
+
update(ends_at: new_ends_at)
|
15
15
|
rescue ::Stripe::StripeError => e
|
16
16
|
raise Error, e.message
|
17
17
|
end
|
data/lib/pay/stripe/webhooks.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "stripe_event"
|
2
|
-
Dir[File.join(__dir__, "webhooks", "**", "*.rb")].each { |file| require file }
|
2
|
+
Dir[File.join(__dir__, "webhooks", "**", "*.rb")].sort.each { |file| require file }
|
3
3
|
|
4
4
|
StripeEvent.configure do |events|
|
5
5
|
# Listen to the charge event to make sure we get non-subscription
|
@@ -4,23 +4,20 @@ module Pay
|
|
4
4
|
class ChargeSucceeded
|
5
5
|
def call(event)
|
6
6
|
object = event.data.object
|
7
|
-
|
8
|
-
processor: :stripe,
|
9
|
-
processor_id: object.customer
|
10
|
-
)
|
7
|
+
billable = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
11
8
|
|
12
|
-
return unless
|
13
|
-
return if
|
9
|
+
return unless billable.present?
|
10
|
+
return if billable.charges.where(processor_id: object.id).any?
|
14
11
|
|
15
|
-
charge = create_charge(
|
16
|
-
notify_user(
|
12
|
+
charge = create_charge(billable, object)
|
13
|
+
notify_user(billable, charge)
|
17
14
|
charge
|
18
15
|
end
|
19
16
|
|
20
17
|
def create_charge(user, object)
|
21
18
|
charge = user.charges.find_or_initialize_by(
|
22
19
|
processor: :stripe,
|
23
|
-
processor_id: object.id
|
20
|
+
processor_id: object.id
|
24
21
|
)
|
25
22
|
|
26
23
|
charge.update(
|
@@ -4,23 +4,23 @@ module Pay
|
|
4
4
|
class CustomerDeleted
|
5
5
|
def call(event)
|
6
6
|
object = event.data.object
|
7
|
-
|
7
|
+
billable = Pay.find_billable(processor: :stripe, processor_id: object.id)
|
8
8
|
|
9
9
|
# Couldn't find user, we can skip
|
10
|
-
return unless
|
10
|
+
return unless billable.present?
|
11
11
|
|
12
|
-
|
12
|
+
billable.update(
|
13
13
|
processor_id: nil,
|
14
14
|
trial_ends_at: nil,
|
15
15
|
card_type: nil,
|
16
16
|
card_last4: nil,
|
17
17
|
card_exp_month: nil,
|
18
|
-
card_exp_year: nil
|
18
|
+
card_exp_year: nil
|
19
19
|
)
|
20
20
|
|
21
|
-
|
21
|
+
billable.subscriptions.update_all(
|
22
22
|
trial_ends_at: nil,
|
23
|
-
ends_at: Time.zone.now
|
23
|
+
ends_at: Time.zone.now
|
24
24
|
)
|
25
25
|
end
|
26
26
|
end
|
@@ -4,12 +4,12 @@ module Pay
|
|
4
4
|
class CustomerUpdated
|
5
5
|
def call(event)
|
6
6
|
object = event.data.object
|
7
|
-
|
7
|
+
billable = Pay.find_billable(processor: :stripe, processor_id: object.id)
|
8
8
|
|
9
9
|
# Couldn't find user, we can skip
|
10
|
-
return unless
|
10
|
+
return unless billable.present?
|
11
11
|
|
12
|
-
|
12
|
+
billable.sync_card_from_stripe
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -4,12 +4,12 @@ module Pay
|
|
4
4
|
class PaymentMethodUpdated
|
5
5
|
def call(event)
|
6
6
|
object = event.data.object
|
7
|
-
|
7
|
+
billable = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
8
8
|
|
9
9
|
# Couldn't find user, we can skip
|
10
|
-
return unless
|
10
|
+
return unless billable.present?
|
11
11
|
|
12
|
-
|
12
|
+
billable.sync_card_from_stripe
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -8,12 +8,15 @@ module Pay
|
|
8
8
|
# We may already have the subscription in the database, so we can update that record
|
9
9
|
subscription = Pay.subscription_model.find_by(processor: :stripe, processor_id: object.id)
|
10
10
|
|
11
|
+
# Create the subscription in the database if we don't have it already
|
11
12
|
if subscription.nil?
|
12
13
|
# The customer should already be in the database
|
13
|
-
owner = Pay.
|
14
|
+
owner = Pay.find_billable(processor: :stripe, processor_id: object.customer)
|
14
15
|
|
15
|
-
|
16
|
-
|
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
|
17
20
|
|
18
21
|
subscription = Pay.subscription_model.new(owner: owner)
|
19
22
|
end
|
data/lib/pay/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Charnes
|
8
8
|
- Chris Oliver
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-06-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -73,20 +73,6 @@ dependencies:
|
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '2.3'
|
76
|
-
- !ruby/object:Gem::Dependency
|
77
|
-
name: bundler
|
78
|
-
requirement: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
type: :development
|
84
|
-
prerelease: false
|
85
|
-
version_requirements: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
76
|
- !ruby/object:Gem::Dependency
|
91
77
|
name: byebug
|
92
78
|
requirement: !ruby/object:Gem::Requirement
|
@@ -233,10 +219,13 @@ files:
|
|
233
219
|
- config/locales/en.yml
|
234
220
|
- config/routes.rb
|
235
221
|
- db/migrate/20170205020145_create_pay_subscriptions.rb
|
236
|
-
- db/migrate/20170503131610_add_fields_to_billable.rb
|
237
222
|
- db/migrate/20170727235816_create_pay_charges.rb
|
238
223
|
- db/migrate/20190816015720_add_status_to_pay_subscriptions.rb
|
224
|
+
- lib/generators/active_record/pay_generator.rb
|
225
|
+
- lib/generators/active_record/templates/migration.rb
|
239
226
|
- lib/generators/pay/email_views_generator.rb
|
227
|
+
- lib/generators/pay/orm_helpers.rb
|
228
|
+
- lib/generators/pay/pay_generator.rb
|
240
229
|
- lib/generators/pay/views_generator.rb
|
241
230
|
- lib/pay.rb
|
242
231
|
- lib/pay/billable.rb
|
@@ -269,7 +258,7 @@ homepage: https://github.com/jasoncharnes/pay
|
|
269
258
|
licenses:
|
270
259
|
- MIT
|
271
260
|
metadata: {}
|
272
|
-
post_install_message:
|
261
|
+
post_install_message:
|
273
262
|
rdoc_options: []
|
274
263
|
require_paths:
|
275
264
|
- lib
|
@@ -284,8 +273,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
284
273
|
- !ruby/object:Gem::Version
|
285
274
|
version: '0'
|
286
275
|
requirements: []
|
287
|
-
rubygems_version: 3.1.
|
288
|
-
signing_key:
|
276
|
+
rubygems_version: 3.1.4
|
277
|
+
signing_key:
|
289
278
|
specification_version: 4
|
290
279
|
summary: A Ruby on Rails subscription engine.
|
291
280
|
test_files: []
|
@@ -1,16 +0,0 @@
|
|
1
|
-
class AddFieldsToBillable < ActiveRecord::Migration[4.2]
|
2
|
-
def change
|
3
|
-
unless ActiveRecord::Base.connection.table_exists?(Pay.billable_table)
|
4
|
-
create_table Pay.billable_table.to_sym
|
5
|
-
end
|
6
|
-
|
7
|
-
add_column Pay.billable_table, :processor, :string
|
8
|
-
add_column Pay.billable_table, :processor_id, :string
|
9
|
-
add_column Pay.billable_table, :trial_ends_at, :datetime
|
10
|
-
add_column Pay.billable_table, :card_type, :string
|
11
|
-
add_column Pay.billable_table, :card_last4, :string
|
12
|
-
add_column Pay.billable_table, :card_exp_month, :string
|
13
|
-
add_column Pay.billable_table, :card_exp_year, :string
|
14
|
-
add_column Pay.billable_table, :extra_billing_info, :text
|
15
|
-
end
|
16
|
-
end
|