pay 2.2.2 → 2.3.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 +137 -7
- data/Rakefile +2 -4
- data/app/controllers/pay/payments_controller.rb +2 -0
- data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
- data/app/models/pay/application_record.rb +6 -1
- data/app/models/pay/charge.rb +7 -0
- data/app/models/pay/subscription.rb +24 -3
- data/config/routes.rb +1 -0
- data/db/migrate/20200603134434_add_data_to_pay_models.rb +17 -0
- data/lib/pay.rb +3 -0
- data/lib/pay/billable.rb +5 -0
- data/lib/pay/braintree/billable.rb +10 -4
- data/lib/pay/braintree/charge.rb +4 -0
- data/lib/pay/braintree/subscription.rb +6 -0
- data/lib/pay/engine.rb +7 -0
- data/lib/pay/paddle.rb +38 -0
- data/lib/pay/paddle/billable.rb +66 -0
- data/lib/pay/paddle/charge.rb +39 -0
- data/lib/pay/paddle/subscription.rb +59 -0
- data/lib/pay/paddle/webhooks.rb +1 -0
- data/lib/pay/paddle/webhooks/signature_verifier.rb +115 -0
- data/lib/pay/paddle/webhooks/subscription_cancelled.rb +18 -0
- data/lib/pay/paddle/webhooks/subscription_created.rb +59 -0
- data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +21 -0
- data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +64 -0
- data/lib/pay/paddle/webhooks/subscription_updated.rb +34 -0
- data/lib/pay/stripe/billable.rb +6 -0
- data/lib/pay/stripe/charge.rb +4 -0
- data/lib/pay/stripe/subscription.rb +6 -0
- data/lib/pay/stripe/webhooks/charge_succeeded.rb +7 -7
- data/lib/pay/stripe/webhooks/payment_action_required.rb +7 -8
- data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
- data/lib/pay/version.rb +1 -1
- metadata +59 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd6aa7121588fb74663d51c6fdb21d0a67b196cc1f125921ae208ad65758fb6c
|
4
|
+
data.tar.gz: f15391bc0601b3027f19ec2c5111103fd0d739b24a48366e2f3c67489c2b99c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1539032c93fbcdf7a8ad83b51bff67451fcaac73553950ae916d2e67d78ad8257a34cc7729e51ed9243eb04270a25025e87286b2dbb65345fae5314b8dc370de
|
7
|
+
data.tar.gz: 73ba465dbf96a4af3f13091219ed4af9ad551ecb9f8a53b6a6b781720704b2f4aaf87474c526fca637023e576d6fc37e6c2c98d9c665550cda8691492a80f506
|
data/README.md
CHANGED
@@ -10,6 +10,7 @@ Pay is a payments engine for Ruby on Rails 4.2 and higher.
|
|
10
10
|
|
11
11
|
- Stripe ([supports SCA](https://stripe.com/docs/strong-customer-authentication) using API version `2020-08-27`)
|
12
12
|
- Braintree
|
13
|
+
- Paddle
|
13
14
|
|
14
15
|
Want to add a new payment provider? Contributions are welcome and the instructions [are here](https://github.com/jasoncharnes/pay/wiki/New-Payment-Provider).
|
15
16
|
|
@@ -35,6 +36,9 @@ gem 'stripe_event', '~> 2.3'
|
|
35
36
|
# To use Braintree + PayPal, also include:
|
36
37
|
gem 'braintree', '< 3.0', '>= 2.92.0'
|
37
38
|
|
39
|
+
# To use Paddle, also include:
|
40
|
+
gem 'paddle_pay', '~> 0.0.1'
|
41
|
+
|
38
42
|
# To use Receipts
|
39
43
|
gem 'receipts', '~> 1.0.0'
|
40
44
|
```
|
@@ -133,10 +137,15 @@ development:
|
|
133
137
|
public_key: yyyy
|
134
138
|
merchant_id: aaaa
|
135
139
|
environment: sandbox
|
140
|
+
paddle:
|
141
|
+
vendor_id: xxxx
|
142
|
+
vendor_auth_code: yyyy
|
143
|
+
public_key_base64: MII...==
|
136
144
|
```
|
137
145
|
|
138
146
|
For Stripe, you can also use the `STRIPE_PUBLIC_KEY`, `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
|
139
147
|
For Braintree, you can also use `BRAINTREE_MERCHANT_ID`, `BRAINTREE_PUBLIC_KEY`, `BRAINTREE_PRIVATE_KEY`, and `BRAINTREE_ENVIRONMENT` environment variables.
|
148
|
+
For Paddle, you can also use `PADDLE_VENDOR_ID`, `PADDLE_VENDOR_AUTH_CODE` and `PADDLE_PUBLIC_KEY_BASE64` environment variables.
|
140
149
|
|
141
150
|
### Generators
|
142
151
|
|
@@ -196,6 +205,8 @@ user.on_generic_trial? #=> true
|
|
196
205
|
|
197
206
|
#### Creating a Charge
|
198
207
|
|
208
|
+
##### Stripe and Braintree
|
209
|
+
|
199
210
|
```ruby
|
200
211
|
user = User.find_by(email: 'michael@bluthcompany.co')
|
201
212
|
|
@@ -219,8 +230,23 @@ different currencies, etc.
|
|
219
230
|
On failure, a `Pay::Error` will be raised with details about the payment
|
220
231
|
failure.
|
221
232
|
|
233
|
+
##### Paddle
|
234
|
+
It is only possible to create immediate one-time charges on top of an existing subscription.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
user = User.find_by(email: 'michael@bluthcompany.co')
|
238
|
+
|
239
|
+
user.processor = 'paddle'
|
240
|
+
user.charge(1500, {charge_name: "Test"}) # $15.00 USD
|
241
|
+
|
242
|
+
```
|
243
|
+
|
244
|
+
An existing subscription and a charge name are required.
|
245
|
+
|
222
246
|
#### Creating a Subscription
|
223
247
|
|
248
|
+
##### Stripe and Braintree
|
249
|
+
|
224
250
|
```ruby
|
225
251
|
user = User.find_by(email: 'michael@bluthcompany.co')
|
226
252
|
|
@@ -246,20 +272,53 @@ For example, you can pass the `quantity` option to subscribe to a plan with for
|
|
246
272
|
user.subscribe(name: "default", plan: "default", quantity: 3)
|
247
273
|
```
|
248
274
|
|
249
|
-
|
275
|
+
###### Name
|
250
276
|
|
251
277
|
Name is an internally used name for the subscription.
|
252
278
|
|
253
|
-
|
279
|
+
###### Plan
|
254
280
|
|
255
281
|
Plan is the plan ID or price ID from the payment processor. For example: `plan_xxxxx` or `price_xxxxx`
|
256
282
|
|
257
|
-
|
283
|
+
###### Options
|
258
284
|
|
259
285
|
By default, the trial specified on the subscription will be used.
|
260
286
|
|
261
287
|
`trial_period_days: 30` can be set to override and a trial to the subscription. This works the same for Braintree and Stripe.
|
262
288
|
|
289
|
+
##### Paddle
|
290
|
+
It is currently not possible to create a subscription through the API. Instead the subscription in Pay is created by the Paddle Subscription Webhook. In order to be able to assign the subcription to the correct owner, the Paddle [passthrough parameter](https://developer.paddle.com/guides/how-tos/checkout/pass-parameters) has to be used for checkout.
|
291
|
+
|
292
|
+
To ensure that the owner cannot be tampered with, Pay uses a Signed Global ID with a purpose. The purpose string consists of "paddle_" and the subscription plan id (or product id respectively).
|
293
|
+
|
294
|
+
Javascript Checkout:
|
295
|
+
```javascript
|
296
|
+
Paddle.Checkout.open({
|
297
|
+
product: 12345,
|
298
|
+
passthrough: "<%= Pay::Paddle.passthrough(owner: current_user) %>"
|
299
|
+
});
|
300
|
+
```
|
301
|
+
|
302
|
+
Paddle Button Checkout:
|
303
|
+
```html
|
304
|
+
<a href="#!" class="paddle_button" data-product="12345" data-email="<%= current_user.email %>" data-passthrough="<%= Pay::Paddle.passthrough(owner: current_user) %>"
|
305
|
+
```
|
306
|
+
|
307
|
+
###### Passthrough
|
308
|
+
|
309
|
+
Pay providers a helper method for generating the passthrough JSON object to associate the purchase with the correct Rails model.
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
Pay::Paddle.passthrough(owner: current_user, foo: :bar)
|
313
|
+
#=> { owner_sgid: "xxxxxxxx", foo: "bar" }
|
314
|
+
|
315
|
+
# To generate manually without the helper
|
316
|
+
#=> { owner_sgid: current_user.to_sgid.to_s, foo: "bar" }.to_json
|
317
|
+
```
|
318
|
+
|
319
|
+
Pay parses the passthrough JSON string and verifies the `owner_sgid` hash to match the webhook with the correct billable record.
|
320
|
+
The passthrough parameter `owner_sgid` is only required for creating a subscription.
|
321
|
+
|
263
322
|
#### Retrieving a Subscription from the Database
|
264
323
|
|
265
324
|
```ruby
|
@@ -316,26 +375,45 @@ Plan is the plan ID from the payment processor.
|
|
316
375
|
|
317
376
|
#### Retrieving a Payment Processor Account
|
318
377
|
|
378
|
+
##### Stripe and Braintree
|
379
|
+
|
319
380
|
```ruby
|
320
381
|
user = User.find_by(email: 'george.michael@bluthcompany.co')
|
321
382
|
|
322
383
|
user.customer #> Stripe or Braintree customer account
|
323
384
|
```
|
324
385
|
|
386
|
+
##### Paddle
|
387
|
+
|
388
|
+
It is currently not possible to retrieve a payment processor account through the API.
|
389
|
+
|
325
390
|
#### Updating a Customer's Credit Card
|
326
391
|
|
392
|
+
##### Stripe and Braintree
|
393
|
+
|
327
394
|
```ruby
|
328
395
|
user = User.find_by(email: 'tobias@bluthcompany.co')
|
329
396
|
|
330
397
|
user.update_card('payment_method_id')
|
331
398
|
```
|
332
399
|
|
400
|
+
##### Paddle
|
401
|
+
|
402
|
+
Paddle provides a unique [Update URL](https://developer.paddle.com/guides/how-tos/subscriptions/update-payment-details) for each user, which allows them to update the payment method.
|
403
|
+
```ruby
|
404
|
+
user = User.find_by(email: 'tobias@bluthcompany.co')
|
405
|
+
|
406
|
+
user.subscription.paddle_update_url
|
407
|
+
```
|
408
|
+
|
409
|
+
|
410
|
+
|
333
411
|
#### Retrieving a Customer's Subscription from the Processor
|
334
412
|
|
335
413
|
```ruby
|
336
414
|
user = User.find_by(email: 'lucille@bluthcompany.co')
|
337
415
|
|
338
|
-
user.processor_subscription(subscription_id) #=> Stripe or
|
416
|
+
user.processor_subscription(subscription_id) #=> Stripe, Braintree or Paddle Subscription
|
339
417
|
```
|
340
418
|
|
341
419
|
## Subscription API
|
@@ -372,14 +450,31 @@ user = User.find_by(email: 'carl.weathers@bluthcompany.co')
|
|
372
450
|
user.subscription.active? #=> true or false
|
373
451
|
```
|
374
452
|
|
453
|
+
#### Checking to See If a Subscription Is Paused
|
454
|
+
|
455
|
+
```ruby
|
456
|
+
user = User.find_by(email: 'carl.weathers@bluthcompany.co')
|
457
|
+
|
458
|
+
user.subscription.paused? #=> true or false
|
459
|
+
```
|
460
|
+
|
375
461
|
#### Cancel a Subscription (At End of Billing Cycle)
|
376
462
|
|
463
|
+
##### Stripe, Braintree and Paddle
|
464
|
+
|
377
465
|
```ruby
|
378
466
|
user = User.find_by(email: 'oscar@bluthcompany.co')
|
379
467
|
|
380
468
|
user.subscription.cancel
|
381
469
|
```
|
382
470
|
|
471
|
+
##### Paddle
|
472
|
+
In addition to the API, Paddle provides a subscription [Cancel URL](https://developer.paddle.com/guides/how-tos/subscriptions/cancel-and-pause) that you can redirect customers to cancel their subscription.
|
473
|
+
|
474
|
+
```ruby
|
475
|
+
user.subscription.paddle_cancel_url
|
476
|
+
```
|
477
|
+
|
383
478
|
#### Cancel a Subscription Immediately
|
384
479
|
|
385
480
|
```ruby
|
@@ -388,6 +483,16 @@ user = User.find_by(email: 'annyong@bluthcompany.co')
|
|
388
483
|
user.subscription.cancel_now!
|
389
484
|
```
|
390
485
|
|
486
|
+
#### Pause a Subscription
|
487
|
+
|
488
|
+
##### Paddle
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
user = User.find_by(email: 'oscar@bluthcompany.co')
|
492
|
+
|
493
|
+
user.subscription.pause
|
494
|
+
```
|
495
|
+
|
391
496
|
#### Swap a Subscription to another Plan
|
392
497
|
|
393
498
|
```ruby
|
@@ -396,7 +501,17 @@ user = User.find_by(email: 'steve.holt@bluthcompany.co')
|
|
396
501
|
user.subscription.swap("yearly")
|
397
502
|
```
|
398
503
|
|
399
|
-
#### Resume a Subscription
|
504
|
+
#### Resume a Subscription
|
505
|
+
|
506
|
+
##### Stripe or Braintree Subscription (on Grace Period)
|
507
|
+
|
508
|
+
```ruby
|
509
|
+
user = User.find_by(email: 'steve.holt@bluthcompany.co')
|
510
|
+
|
511
|
+
user.subscription.resume
|
512
|
+
```
|
513
|
+
|
514
|
+
##### Paddle (Paused)
|
400
515
|
|
401
516
|
```ruby
|
402
517
|
user = User.find_by(email: 'steve.holt@bluthcompany.co')
|
@@ -473,8 +588,8 @@ config.routes_path = '/secret-webhook-path'
|
|
473
588
|
|
474
589
|
## Payment Providers
|
475
590
|
|
476
|
-
We support
|
477
|
-
standardize the
|
591
|
+
We support Stripe, Braintree and Paddle and make our best attempt to
|
592
|
+
standardize the three. They function differently so keep that in mind if
|
478
593
|
you plan on doing more complex payments. It would be best to stick with
|
479
594
|
a single payment provider in that case so you don't run into
|
480
595
|
discrepancies.
|
@@ -489,7 +604,22 @@ development:
|
|
489
604
|
merchant_id: zzzz
|
490
605
|
environment: sandbox
|
491
606
|
```
|
607
|
+
#### Paddle
|
608
|
+
|
609
|
+
```yaml
|
610
|
+
paddle:
|
611
|
+
vendor_id: xxxx
|
612
|
+
vendor_auth_code: yyyy
|
613
|
+
public_key_base64: MII...==
|
614
|
+
```
|
492
615
|
|
616
|
+
Paddle receipts can be retrieved by a charge receipt URL.
|
617
|
+
```ruby
|
618
|
+
user = User.find_by(email: 'annyong@bluthcompany.co')
|
619
|
+
|
620
|
+
charge = user.charges.first
|
621
|
+
charge.paddle_receipt_url
|
622
|
+
```
|
493
623
|
#### Stripe
|
494
624
|
|
495
625
|
You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
|
data/Rakefile
CHANGED
@@ -4,6 +4,8 @@ rescue LoadError
|
|
4
4
|
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
5
5
|
end
|
6
6
|
|
7
|
+
require "bundler/gem_tasks"
|
8
|
+
|
7
9
|
require "rdoc/task"
|
8
10
|
|
9
11
|
RDoc::Task.new(:rdoc) do |rdoc|
|
@@ -19,10 +21,6 @@ load "rails/tasks/engine.rake"
|
|
19
21
|
|
20
22
|
load "rails/tasks/statistics.rake"
|
21
23
|
|
22
|
-
unless Rails.env.test?
|
23
|
-
require "bundler/gem_tasks"
|
24
|
-
end
|
25
|
-
|
26
24
|
require "rake/testtask"
|
27
25
|
|
28
26
|
Rake::TestTask.new(:test) do |t|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Pay
|
2
|
+
module Webhooks
|
3
|
+
class PaddleController < Pay::ApplicationController
|
4
|
+
if Rails.application.config.action_controller.default_protect_from_forgery
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
verifier = Pay::Paddle::Webhooks::SignatureVerifier.new(check_params.as_json)
|
10
|
+
if verifier.verify
|
11
|
+
case params["alert_name"]
|
12
|
+
when "subscription_created"
|
13
|
+
Pay::Paddle::Webhooks::SubscriptionCreated.new(check_params.as_json)
|
14
|
+
when "subscription_updated"
|
15
|
+
Pay::Paddle::Webhooks::SubscriptionUpdated.new(check_params.as_json)
|
16
|
+
when "subscription_cancelled"
|
17
|
+
Pay::Paddle::Webhooks::SubscriptionCancelled.new(check_params.as_json)
|
18
|
+
when "subscription_payment_succeeded"
|
19
|
+
Pay::Paddle::Webhooks::SubscriptionPaymentSucceeded.new(check_params.as_json)
|
20
|
+
when "subscription_payment_refunded"
|
21
|
+
Pay::Paddle::Webhooks::SubscriptionPaymentRefunded.new(check_params.as_json)
|
22
|
+
end
|
23
|
+
render json: {success: true}, status: :ok
|
24
|
+
else
|
25
|
+
head :ok
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def check_params
|
32
|
+
params.except(:action, :controller).permit!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,5 +1,10 @@
|
|
1
1
|
module Pay
|
2
|
-
class ApplicationRecord <
|
2
|
+
class ApplicationRecord < Pay.model_parent_class.constantize
|
3
3
|
self.abstract_class = true
|
4
|
+
|
5
|
+
def self.json_column?(name)
|
6
|
+
return unless connected? && table_exists?
|
7
|
+
[:json, :jsonb].include?(attribute_types[name].type)
|
8
|
+
end
|
4
9
|
end
|
5
10
|
end
|
data/app/models/pay/charge.rb
CHANGED
@@ -2,6 +2,9 @@ module Pay
|
|
2
2
|
class Charge < ApplicationRecord
|
3
3
|
self.table_name = Pay.chargeable_table
|
4
4
|
|
5
|
+
# Only serialize for non-json columns
|
6
|
+
serialize :data unless json_column?("data")
|
7
|
+
|
5
8
|
# Associations
|
6
9
|
belongs_to :owner, polymorphic: true
|
7
10
|
|
@@ -39,5 +42,9 @@ module Pay
|
|
39
42
|
def paypal?
|
40
43
|
braintree? && card_type == "PayPal"
|
41
44
|
end
|
45
|
+
|
46
|
+
def paddle?
|
47
|
+
processor == "paddle"
|
48
|
+
end
|
42
49
|
end
|
43
50
|
end
|
@@ -2,7 +2,10 @@ module Pay
|
|
2
2
|
class Subscription < ApplicationRecord
|
3
3
|
self.table_name = Pay.subscription_table
|
4
4
|
|
5
|
-
STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid]
|
5
|
+
STATUSES = %w[incomplete incomplete_expired trialing active past_due canceled unpaid paused]
|
6
|
+
|
7
|
+
# Only serialize for non-json columns
|
8
|
+
serialize :data unless json_column?("data")
|
6
9
|
|
7
10
|
# Associations
|
8
11
|
belongs_to :owner, polymorphic: true
|
@@ -66,6 +69,15 @@ module Pay
|
|
66
69
|
past_due? || incomplete?
|
67
70
|
end
|
68
71
|
|
72
|
+
def paused?
|
73
|
+
status == "paused"
|
74
|
+
end
|
75
|
+
|
76
|
+
def pause
|
77
|
+
return unless paddle?
|
78
|
+
send("#{processor}_pause")
|
79
|
+
end
|
80
|
+
|
69
81
|
def cancel
|
70
82
|
send("#{processor}_cancel")
|
71
83
|
end
|
@@ -76,8 +88,17 @@ module Pay
|
|
76
88
|
|
77
89
|
def resume
|
78
90
|
unless on_grace_period?
|
79
|
-
|
80
|
-
|
91
|
+
unless paddle?
|
92
|
+
raise StandardError,
|
93
|
+
"You can only resume subscriptions within their grace period."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
unless paused?
|
98
|
+
if paddle?
|
99
|
+
raise StandardError,
|
100
|
+
"You can only resume paused subscriptions."
|
101
|
+
end
|
81
102
|
end
|
82
103
|
|
83
104
|
send("#{processor}_resume")
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
class AddDataToPayModels < ActiveRecord::Migration[4.2]
|
2
|
+
def change
|
3
|
+
add_column :pay_subscriptions, :data, data_column_type
|
4
|
+
add_column :pay_charges, :data, data_column_type
|
5
|
+
end
|
6
|
+
|
7
|
+
def data_column_type
|
8
|
+
case ActiveRecord::Base.configurations.default_hash.dig("adapter")
|
9
|
+
when "mysql2"
|
10
|
+
:json
|
11
|
+
when "postgresql"
|
12
|
+
:jsonb
|
13
|
+
else
|
14
|
+
:text
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|