pay 2.0.3 → 2.2.0
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 +101 -117
- 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 +1 -1
- data/db/migrate/20170205020145_create_pay_subscriptions.rb +2 -1
- data/db/migrate/20170727235816_create_pay_charges.rb +2 -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/receipts.rb +2 -2
- data/lib/pay/stripe.rb +1 -1
- data/lib/pay/stripe/billable.rb +9 -6
- 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 +7 -18
- 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: ba84d79847e4ef8659bcf7fce74b966c07bb93fa8e2f29f902a243c9d6e5c8bf
|
4
|
+
data.tar.gz: 78e829fc4d218aabee2544a969b184e29e2621bf0d965318d1b33e4e750ebae4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c044f842d680969455c49a24124c48e2e80a1b06486db11782032ebdd0db810c1836151f97087bb1e06e083106524e9250520768a96462ff24fdc0723a4c5dd
|
7
|
+
data.tar.gz: 7dbe7db2d4c6161b9516f73a1496092b6ffb9ebf2c5471fe061ccfd79b1335bc0a3238c1e407f621e3717cbd0cf28496790153b884a8540557428b6cf33c665c
|
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) [![Gem Version](https://badge.fury.io/rb/pay.svg)](https://badge.fury.io/rb/pay)
|
8
6
|
|
9
7
|
Pay is a payments engine for Ruby on Rails 4.2 and higher.
|
10
8
|
|
@@ -15,6 +13,14 @@ 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:
|
@@ -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
|
-
|
52
|
+
`bin/rails pay:install:migrations`
|
57
53
|
|
58
|
-
|
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.
|
59
55
|
|
60
|
-
|
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
|
56
|
+
`bin/rails g pay User`
|
64
57
|
|
65
|
-
|
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.
|
66
59
|
|
67
|
-
|
68
|
-
module in it. By default, we assume this is `User`.
|
60
|
+
Finally, run the migrations
|
69
61
|
|
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
|
-
```
|
84
|
-
|
85
|
-
#### Run the Migrations
|
86
|
-
|
87
|
-
Finally, run the migrations with `$ rake db:migrate`
|
62
|
+
`rake db:migrate`
|
88
63
|
|
89
64
|
#### Getting NoMethodError?
|
90
65
|
|
@@ -92,78 +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
|
-
#### Braintree
|
104
|
-
|
105
|
-
```yaml
|
106
|
-
development:
|
107
|
-
braintree:
|
108
|
-
private_key: xxxx
|
109
|
-
public_key: yyyy
|
110
|
-
merchant_id: zzzz
|
111
|
-
environment: sandbox
|
112
|
-
```
|
113
|
-
|
114
|
-
#### Stripe
|
115
|
-
|
116
|
-
You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
|
117
|
-
|
118
|
-
```yaml
|
119
|
-
development:
|
120
|
-
stripe:
|
121
|
-
private_key: xxxx
|
122
|
-
public_key: yyyy
|
123
|
-
signing_secret: zzzz
|
124
|
-
```
|
125
|
-
|
126
|
-
You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
|
127
|
-
|
128
|
-
**To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
|
129
|
-
|
130
|
-
##### Strong Customer Authentication (SCA)
|
131
|
-
|
132
|
-
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.
|
133
|
-
|
134
|
-
Subscriptions that require SCA are marked as `incomplete` by default.
|
135
|
-
Once payment is authenticated, Stripe will send a webhook updating the
|
136
|
-
status of the subscription. You'll need to use the [Stripe CLI](https://github.com/stripe/stripe-cli) to forward
|
137
|
-
webhooks to your application to make sure your subscriptions work
|
138
|
-
correctly for SCA payments.
|
139
|
-
|
140
|
-
```bash
|
141
|
-
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
|
142
|
-
```
|
143
|
-
|
144
|
-
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).
|
145
|
-
|
146
|
-
The Javascript will now need to use createPaymentMethod instead of createToken. https://stripe.com/docs/js/payment_intents/create_payment_method
|
147
|
-
|
148
|
-
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.
|
149
|
-
|
150
|
-
**Payment Confirmations**
|
151
|
-
|
152
|
-
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.
|
153
|
-
|
154
|
-
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.
|
155
|
-
|
156
|
-
[<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)
|
157
|
-
|
158
|
-
#### Background jobs
|
159
|
-
|
160
|
-
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.
|
161
|
-
|
162
|
-
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)
|
163
|
-
|
164
70
|
## Usage
|
165
71
|
|
166
|
-
|
72
|
+
The `Pay::Billable` module should be included in the models you want to make payments and subscriptions.
|
167
73
|
|
168
74
|
```ruby
|
169
75
|
# app/models/user.rb
|
@@ -172,6 +78,8 @@ class User < ActiveRecord::Base
|
|
172
78
|
end
|
173
79
|
```
|
174
80
|
|
81
|
+
An `email` attribute or method on your `Billable` model is required.
|
82
|
+
|
175
83
|
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.
|
176
84
|
|
177
85
|
## Configuration
|
@@ -180,9 +88,6 @@ Need to make some changes to how Pay is used? You can create an initializer `con
|
|
180
88
|
|
181
89
|
```ruby
|
182
90
|
Pay.setup do |config|
|
183
|
-
config.billable_class = 'User'
|
184
|
-
config.billable_table = 'users'
|
185
|
-
|
186
91
|
config.chargeable_class = 'Pay::Charge'
|
187
92
|
config.chargeable_table = 'pay_charges'
|
188
93
|
|
@@ -255,6 +160,7 @@ Emails can be enabled/disabled using the `send_emails` configuration option (ena
|
|
255
160
|
- When a charge was refunded
|
256
161
|
- When a subscription is about to renew
|
257
162
|
|
163
|
+
|
258
164
|
## Billable API
|
259
165
|
|
260
166
|
#### Trials
|
@@ -310,6 +216,9 @@ You may pass optional arguments that will be directly passed on to
|
|
310
216
|
either Stripe or Braintree. You can use these options to charge
|
311
217
|
different currencies, etc.
|
312
218
|
|
219
|
+
On failure, a `Pay::Error` will be raised with details about the payment
|
220
|
+
failure.
|
221
|
+
|
313
222
|
#### Creating a Subscription
|
314
223
|
|
315
224
|
```ruby
|
@@ -330,13 +239,20 @@ def subscribe(name: 'default', plan: 'default', **options)
|
|
330
239
|
end
|
331
240
|
```
|
332
241
|
|
242
|
+
For example, you can pass the `quantity` option to subscribe to a plan with for per-seat pricing.
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
|
246
|
+
user.subscribe(name: "default", plan: "default", quantity: 3)
|
247
|
+
```
|
248
|
+
|
333
249
|
##### Name
|
334
250
|
|
335
251
|
Name is an internally used name for the subscription.
|
336
252
|
|
337
253
|
##### Plan
|
338
254
|
|
339
|
-
Plan is the plan ID from the payment processor.
|
255
|
+
Plan is the plan ID or price ID from the payment processor. For example: `plan_xxxxx` or `price_xxxxx`
|
340
256
|
|
341
257
|
##### Options
|
342
258
|
|
@@ -555,6 +471,74 @@ If you just want to modify where the engine mounts it's routes then you can chan
|
|
555
471
|
config.routes_path = '/secret-webhook-path'
|
556
472
|
```
|
557
473
|
|
474
|
+
## Payment Providers
|
475
|
+
|
476
|
+
We support both Stripe and Braintree and make our best attempt to
|
477
|
+
standardize the two. They function differently so keep that in mind if
|
478
|
+
you plan on doing more complex payments. It would be best to stick with
|
479
|
+
a single payment provider in that case so you don't run into
|
480
|
+
discrepancies.
|
481
|
+
|
482
|
+
#### Braintree
|
483
|
+
|
484
|
+
```yaml
|
485
|
+
development:
|
486
|
+
braintree:
|
487
|
+
private_key: xxxx
|
488
|
+
public_key: yyyy
|
489
|
+
merchant_id: zzzz
|
490
|
+
environment: sandbox
|
491
|
+
```
|
492
|
+
|
493
|
+
#### Stripe
|
494
|
+
|
495
|
+
You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
|
496
|
+
|
497
|
+
```yaml
|
498
|
+
development:
|
499
|
+
stripe:
|
500
|
+
private_key: xxxx
|
501
|
+
public_key: yyyy
|
502
|
+
signing_secret: zzzz
|
503
|
+
```
|
504
|
+
|
505
|
+
You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
|
506
|
+
|
507
|
+
**To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
|
508
|
+
|
509
|
+
##### Strong Customer Authentication (SCA)
|
510
|
+
|
511
|
+
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.
|
512
|
+
|
513
|
+
Subscriptions that require SCA are marked as `incomplete` by default.
|
514
|
+
Once payment is authenticated, Stripe will send a webhook updating the
|
515
|
+
status of the subscription. You'll need to use the [Stripe CLI](https://github.com/stripe/stripe-cli) to forward
|
516
|
+
webhooks to your application to make sure your subscriptions work
|
517
|
+
correctly for SCA payments.
|
518
|
+
|
519
|
+
```bash
|
520
|
+
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
|
521
|
+
```
|
522
|
+
|
523
|
+
You should use `stripe.confirmCardSetup` 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.confirmCardPayment` if you'd like to charge the customer immediately (think checking out of a shopping cart).
|
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
|
+
|
558
542
|
## Contributors
|
559
543
|
|
560
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
|
@@ -1,7 +1,8 @@
|
|
1
1
|
class CreatePaySubscriptions < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
3
|
create_table :pay_subscriptions do |t|
|
4
|
-
|
4
|
+
# Some Billable objects use string as ID, add `type: :string` if needed
|
5
|
+
t.references :owner, polymorphic: true
|
5
6
|
t.string :name, null: false
|
6
7
|
t.string :processor, null: false
|
7
8
|
t.string :processor_id, null: false
|
@@ -1,7 +1,8 @@
|
|
1
1
|
class CreatePayCharges < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
3
|
create_table :pay_charges do |t|
|
4
|
-
|
4
|
+
# Some Billable objects use string as ID, add `type: :string` if needed
|
5
|
+
t.references :owner, polymorphic: true
|
5
6
|
t.string :processor, null: false
|
6
7
|
t.string :processor_id, null: false
|
7
8
|
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::Braintree::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/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
@@ -12,11 +12,11 @@ module Pay
|
|
12
12
|
|
13
13
|
def setup
|
14
14
|
::Stripe.api_key = private_key
|
15
|
+
::Stripe.api_version = '2020-08-27'
|
15
16
|
::StripeEvent.signing_secret = signing_secret
|
16
17
|
|
17
18
|
Pay.charge_model.include Pay::Stripe::Charge
|
18
19
|
Pay.subscription_model.include Pay::Stripe::Subscription
|
19
|
-
Pay.user_model.include Pay::Stripe::Billable
|
20
20
|
end
|
21
21
|
|
22
22
|
def public_key
|
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,20 @@ 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
|
-
|
61
|
+
opts[:customer] = stripe_customer.id
|
62
|
+
|
63
|
+
stripe_sub = ::Stripe::Subscription.create(opts)
|
64
|
+
subscription = create_subscription(stripe_sub, "stripe", name, plan, status: stripe_sub.status, quantity: quantity)
|
62
65
|
|
63
66
|
# No trial, card requires SCA
|
64
67
|
if subscription.incomplete?
|
@@ -37,7 +37,7 @@ module Pay
|
|
37
37
|
subscription = processor_subscription
|
38
38
|
subscription.cancel_at_period_end = false
|
39
39
|
subscription.plan = plan
|
40
|
-
subscription.
|
40
|
+
subscription.proration_behavior = (prorate ? 'create_prorations' : 'none')
|
41
41
|
subscription.trial_end = on_trial? ? trial_ends_at.to_i : "now"
|
42
42
|
subscription.quantity = quantity if quantity?
|
43
43
|
subscription.save
|
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,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.2.0
|
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: 2020-
|
12
|
+
date: 2020-09-01 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
|
@@ -284,7 +273,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
284
273
|
- !ruby/object:Gem::Version
|
285
274
|
version: '0'
|
286
275
|
requirements: []
|
287
|
-
rubygems_version: 3.
|
276
|
+
rubygems_version: 3.1.2
|
288
277
|
signing_key:
|
289
278
|
specification_version: 4
|
290
279
|
summary: A Ruby on Rails subscription engine.
|
@@ -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
|