pay 2.0.3 → 2.1.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 +85 -98
- 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 +3 -3
- data/app/models/pay/charge.rb +1 -1
- data/app/models/pay/subscription.rb +1 -1
- data/db/migrate/20170727235816_create_pay_charges.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 +16 -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 +9 -9
- data/lib/pay/braintree/subscription.rb +8 -8
- data/lib/pay/receipts.rb +2 -2
- data/lib/pay/stripe.rb +0 -1
- data/lib/pay/stripe/billable.rb +3 -3
- 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 +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c17e00d646e15b10c34d222479c5dc636c36b7eb5caa52ef8783613234c71408
|
4
|
+
data.tar.gz: 0f9eed7cf14a994b3aabbb45dbf635e297212a59d1e6ec6f30246f21de458b47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 362c0ba3348eb01c07e7965262bdcbf71c7c19a0e5287899d5821223bc20658e5cb088e480064f0efa3d44d5a16eb0f4eca74513ba89fb1f91f2c790f4068252
|
7
|
+
data.tar.gz: e49bd76481889087ae37095717311a77662aa6c011e6bcd5d1b3c79917b6207f62074a6de07c19f579561d6d17fa86c0247d895b61f68bea71d18804a68edbe9
|
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,6 +13,12 @@ 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
|
+
## Tutorial
|
17
|
+
|
18
|
+
Want to see how Pay works? Check out our video getting started guide.
|
19
|
+
|
20
|
+
<a href="https://www.youtube.com/watch?v=hYlOmqyJIgc" target="_blank"><img width="50%" src="http://i3.ytimg.com/vi/hYlOmqyJIgc/maxresdefault.jpg"></a>
|
21
|
+
|
18
22
|
## Installation
|
19
23
|
|
20
24
|
Add these lines to your application's Gemfile:
|
@@ -55,32 +59,17 @@ To add the migrations to your application, run the following migration:
|
|
55
59
|
|
56
60
|
`$ bin/rails pay:install:migrations`
|
57
61
|
|
58
|
-
This will install
|
62
|
+
This will install three migrations:
|
59
63
|
|
60
64
|
- db/migrate/create_subscriptions.pay.rb
|
61
|
-
- db/migrate/add_fields_to_users.pay.rb
|
62
65
|
- db/migrate/create_charges.pay.rb
|
63
66
|
- db/migrate/add_status_to_subscriptions.pay.rb
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
module in it. By default, we assume this is `User`.
|
68
|
+
You'll also need a model that can make payments. This is called a
|
69
|
+
`Billable` model. The `pay` generator will add fields to the model and
|
70
|
+
add the `Pay::Billable` module to it.
|
69
71
|
|
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
|
-
```
|
72
|
+
`$ bin/rails g pay User`
|
84
73
|
|
85
74
|
#### Run the Migrations
|
86
75
|
|
@@ -92,78 +81,9 @@ Finally, run the migrations with `$ rake db:migrate`
|
|
92
81
|
|
93
82
|
Fully restart your Rails application `bin/spring stop && rails s`
|
94
83
|
|
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
84
|
## Usage
|
165
85
|
|
166
|
-
|
86
|
+
The `Pay::Billable` module should be included in the models you want to make payments and subscriptions.
|
167
87
|
|
168
88
|
```ruby
|
169
89
|
# app/models/user.rb
|
@@ -180,9 +100,6 @@ Need to make some changes to how Pay is used? You can create an initializer `con
|
|
180
100
|
|
181
101
|
```ruby
|
182
102
|
Pay.setup do |config|
|
183
|
-
config.billable_class = 'User'
|
184
|
-
config.billable_table = 'users'
|
185
|
-
|
186
103
|
config.chargeable_class = 'Pay::Charge'
|
187
104
|
config.chargeable_table = 'pay_charges'
|
188
105
|
|
@@ -255,6 +172,7 @@ Emails can be enabled/disabled using the `send_emails` configuration option (ena
|
|
255
172
|
- When a charge was refunded
|
256
173
|
- When a subscription is about to renew
|
257
174
|
|
175
|
+
|
258
176
|
## Billable API
|
259
177
|
|
260
178
|
#### Trials
|
@@ -555,6 +473,75 @@ If you just want to modify where the engine mounts it's routes then you can chan
|
|
555
473
|
config.routes_path = '/secret-webhook-path'
|
556
474
|
```
|
557
475
|
|
476
|
+
## Payment Providers
|
477
|
+
|
478
|
+
We support both Stripe and Braintree and make our best attempt to
|
479
|
+
standardize the two. They function differently so keep that in mind if
|
480
|
+
you plan on doing more complex payments. It would be best to stick with
|
481
|
+
a single payment provider in that case so you don't run into
|
482
|
+
discrepancies.
|
483
|
+
|
484
|
+
#### Braintree
|
485
|
+
|
486
|
+
```yaml
|
487
|
+
development:
|
488
|
+
braintree:
|
489
|
+
private_key: xxxx
|
490
|
+
public_key: yyyy
|
491
|
+
merchant_id: zzzz
|
492
|
+
environment: sandbox
|
493
|
+
```
|
494
|
+
|
495
|
+
#### Stripe
|
496
|
+
|
497
|
+
You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
|
498
|
+
|
499
|
+
```yaml
|
500
|
+
development:
|
501
|
+
stripe:
|
502
|
+
private_key: xxxx
|
503
|
+
public_key: yyyy
|
504
|
+
signing_secret: zzzz
|
505
|
+
```
|
506
|
+
|
507
|
+
You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
|
508
|
+
|
509
|
+
**To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
|
510
|
+
|
511
|
+
##### Strong Customer Authentication (SCA)
|
512
|
+
|
513
|
+
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.
|
514
|
+
|
515
|
+
Subscriptions that require SCA are marked as `incomplete` by default.
|
516
|
+
Once payment is authenticated, Stripe will send a webhook updating the
|
517
|
+
status of the subscription. You'll need to use the [Stripe CLI](https://github.com/stripe/stripe-cli) to forward
|
518
|
+
webhooks to your application to make sure your subscriptions work
|
519
|
+
correctly for SCA payments.
|
520
|
+
|
521
|
+
```bash
|
522
|
+
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
|
523
|
+
```
|
524
|
+
|
525
|
+
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).
|
526
|
+
|
527
|
+
The Javascript will now need to use createPaymentMethod instead of createToken. https://stripe.com/docs/js/payment_intents/create_payment_method
|
528
|
+
|
529
|
+
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.
|
530
|
+
|
531
|
+
**Payment Confirmations**
|
532
|
+
|
533
|
+
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.
|
534
|
+
|
535
|
+
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.
|
536
|
+
|
537
|
+
[<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)
|
538
|
+
|
539
|
+
#### Background jobs
|
540
|
+
|
541
|
+
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.
|
542
|
+
|
543
|
+
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)
|
544
|
+
|
558
545
|
## Contributors
|
559
546
|
|
560
547
|
- [Jason Charnes](https://twitter.com/jmcharnes)
|
@@ -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
|
|
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,7 @@
|
|
1
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
|
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,7 +12,7 @@ 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
17
|
raise Pay::Error.new(result.message) unless result.success?
|
18
18
|
|
@@ -35,7 +35,7 @@ module Pay
|
|
35
35
|
args = {
|
36
36
|
amount: amount / 100.0,
|
37
37
|
customer_id: customer.id,
|
38
|
-
options: {submit_for_settlement: true}
|
38
|
+
options: {submit_for_settlement: true}
|
39
39
|
}.merge(options)
|
40
40
|
|
41
41
|
result = gateway.transaction.sale(args)
|
@@ -78,7 +78,7 @@ module Pay
|
|
78
78
|
payment_method_nonce: token,
|
79
79
|
options: {
|
80
80
|
make_default: true,
|
81
|
-
verify_card: true
|
81
|
+
verify_card: true
|
82
82
|
}
|
83
83
|
)
|
84
84
|
raise Pay::Error.new(result.message) unless result.success?
|
@@ -94,7 +94,7 @@ module Pay
|
|
94
94
|
braintree_customer.update(
|
95
95
|
email: email,
|
96
96
|
first_name: try(:first_name),
|
97
|
-
last_name: try(:last_name)
|
97
|
+
last_name: try(:last_name)
|
98
98
|
)
|
99
99
|
end
|
100
100
|
|
@@ -171,7 +171,7 @@ module Pay
|
|
171
171
|
card_type: payment_method.card_type,
|
172
172
|
card_last4: payment_method.last_4,
|
173
173
|
card_exp_month: payment_method.expiration_month,
|
174
|
-
card_exp_year: payment_method.expiration_year
|
174
|
+
card_exp_year: payment_method.expiration_year
|
175
175
|
}
|
176
176
|
|
177
177
|
when "paypal_account"
|
@@ -179,7 +179,7 @@ module Pay
|
|
179
179
|
card_type: "PayPal",
|
180
180
|
card_last4: transaction.paypal_details.payer_email,
|
181
181
|
card_exp_month: nil,
|
182
|
-
card_exp_year: nil
|
182
|
+
card_exp_year: nil
|
183
183
|
}
|
184
184
|
|
185
185
|
when "android_pay_card"
|
@@ -188,7 +188,7 @@ module Pay
|
|
188
188
|
card_type: payment_method.source_card_type,
|
189
189
|
card_last4: payment_method.source_card_last_4,
|
190
190
|
card_exp_month: payment_method.expiration_month,
|
191
|
-
card_exp_year: payment_method.expiration_year
|
191
|
+
card_exp_year: payment_method.expiration_year
|
192
192
|
}
|
193
193
|
|
194
194
|
when "venmo_account"
|
@@ -196,7 +196,7 @@ module Pay
|
|
196
196
|
card_type: "Venmo",
|
197
197
|
card_last4: transaction.venmo_account_details.username,
|
198
198
|
card_exp_month: nil,
|
199
|
-
card_exp_year: nil
|
199
|
+
card_exp_year: nil
|
200
200
|
}
|
201
201
|
|
202
202
|
when "apple_pay_card"
|
@@ -205,7 +205,7 @@ module Pay
|
|
205
205
|
card_type: payment_method.card_type,
|
206
206
|
card_last4: payment_method.last_4,
|
207
207
|
card_exp_month: payment_method.expiration_month,
|
208
|
-
card_exp_year: payment_method.expiration_year
|
208
|
+
card_exp_year: payment_method.expiration_year
|
209
209
|
}
|
210
210
|
|
211
211
|
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
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)
|
@@ -51,7 +51,7 @@ module Pay
|
|
51
51
|
opts = {
|
52
52
|
expand: ["pending_setup_intent", "latest_invoice.payment_intent"],
|
53
53
|
items: [plan: plan],
|
54
|
-
off_session: true
|
54
|
+
off_session: true
|
55
55
|
}.merge(options)
|
56
56
|
|
57
57
|
# Inherit trial from plan unless trial override was specified
|
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.1.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-03-
|
12
|
+
date: 2020-03-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -236,7 +236,11 @@ files:
|
|
236
236
|
- db/migrate/20170503131610_add_fields_to_billable.rb
|
237
237
|
- db/migrate/20170727235816_create_pay_charges.rb
|
238
238
|
- db/migrate/20190816015720_add_status_to_pay_subscriptions.rb
|
239
|
+
- lib/generators/active_record/pay_generator.rb
|
240
|
+
- lib/generators/active_record/templates/migration.rb
|
239
241
|
- lib/generators/pay/email_views_generator.rb
|
242
|
+
- lib/generators/pay/orm_helpers.rb
|
243
|
+
- lib/generators/pay/pay_generator.rb
|
240
244
|
- lib/generators/pay/views_generator.rb
|
241
245
|
- lib/pay.rb
|
242
246
|
- lib/pay/billable.rb
|