pay 2.1.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.

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +168 -14
  3. data/Rakefile +15 -17
  4. data/app/controllers/pay/payments_controller.rb +3 -0
  5. data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
  6. data/app/mailers/pay/user_mailer.rb +2 -2
  7. data/app/models/pay/application_record.rb +6 -1
  8. data/app/models/pay/charge.rb +7 -0
  9. data/app/models/pay/subscription.rb +24 -3
  10. data/app/views/pay/payments/show.html.erb +1 -1
  11. data/config/routes.rb +1 -0
  12. data/db/migrate/20170205020145_create_pay_subscriptions.rb +2 -1
  13. data/db/migrate/20170727235816_create_pay_charges.rb +1 -0
  14. data/db/migrate/20200603134434_add_data_to_pay_models.rb +17 -0
  15. data/lib/generators/active_record/pay_generator.rb +1 -1
  16. data/lib/generators/pay/orm_helpers.rb +1 -2
  17. data/lib/pay.rb +11 -2
  18. data/lib/pay/billable.rb +7 -2
  19. data/lib/pay/braintree.rb +1 -1
  20. data/lib/pay/braintree/billable.rb +18 -4
  21. data/lib/pay/braintree/charge.rb +4 -0
  22. data/lib/pay/braintree/subscription.rb +6 -0
  23. data/lib/pay/engine.rb +7 -0
  24. data/lib/pay/paddle.rb +38 -0
  25. data/lib/pay/paddle/billable.rb +66 -0
  26. data/lib/pay/paddle/charge.rb +39 -0
  27. data/lib/pay/paddle/subscription.rb +59 -0
  28. data/lib/pay/paddle/webhooks.rb +1 -0
  29. data/lib/pay/paddle/webhooks/signature_verifier.rb +115 -0
  30. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +18 -0
  31. data/lib/pay/paddle/webhooks/subscription_created.rb +59 -0
  32. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +21 -0
  33. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +64 -0
  34. data/lib/pay/paddle/webhooks/subscription_updated.rb +34 -0
  35. data/lib/pay/stripe.rb +1 -0
  36. data/lib/pay/stripe/billable.rb +14 -5
  37. data/lib/pay/stripe/charge.rb +4 -0
  38. data/lib/pay/stripe/subscription.rb +7 -1
  39. data/lib/pay/stripe/webhooks/charge_succeeded.rb +7 -7
  40. data/lib/pay/stripe/webhooks/payment_action_required.rb +7 -8
  41. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
  42. data/lib/pay/version.rb +1 -1
  43. metadata +63 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b8ee10da017b8876cf39df8679d26cf1c18de43201809065693d1c59a4d687f
4
- data.tar.gz: e522b86563b6d6f1770d589f9d3f608168869b9dfc4adc499bb8b6c997669683
3
+ metadata.gz: cd6aa7121588fb74663d51c6fdb21d0a67b196cc1f125921ae208ad65758fb6c
4
+ data.tar.gz: f15391bc0601b3027f19ec2c5111103fd0d739b24a48366e2f3c67489c2b99c3
5
5
  SHA512:
6
- metadata.gz: d775f1b342f3202c2a11c17372e4602fb186bf0c4556ee5e3eae11be4fcd293e5dd719a540a1c3c6f310cc00df9c5802c321b0e503aa4fc102d613562012fee8
7
- data.tar.gz: 8d8bcc8b121760e323dc11ad79db0ac344f07324d157159bb30a6e0b5bf0b55fe6a8c5f5960820031b6808cec411e50ee3abc0d6c4460ccc0c9b7372c99ae250
6
+ metadata.gz: 1539032c93fbcdf7a8ad83b51bff67451fcaac73553950ae916d2e67d78ad8257a34cc7729e51ed9243eb04270a25025e87286b2dbb65345fae5314b8dc370de
7
+ data.tar.gz: 73ba465dbf96a4af3f13091219ed4af9ad551ecb9f8a53b6a6b781720704b2f4aaf87474c526fca637023e576d6fc37e6c2c98d9c665550cda8691492a80f506
data/README.md CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  ## Pay - Payments engine for Ruby on Rails
4
4
 
5
- [![Build Status](https://github.com/pay-rails/pay/workflows/Tests/badge.svg)](https://github.com/pay-rails/pay/actions)
5
+ [![Build Status](https://github.com/pay-rails/pay/workflows/Tests/badge.svg)](https://github.com/pay-rails/pay/actions) [![Gem Version](https://badge.fury.io/rb/pay.svg)](https://badge.fury.io/rb/pay)
6
6
 
7
7
  Pay is a payments engine for Ruby on Rails 4.2 and higher.
8
8
 
9
9
  **Current Payment Providers**
10
10
 
11
- - Stripe ([supports SCA](https://stripe.com/docs/strong-customer-authentication), API version [2019-03-14](https://stripe.com/docs/upgrades#2019-03-14) or higher required)
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
  ```
@@ -78,6 +82,8 @@ class User < ActiveRecord::Base
78
82
  end
79
83
  ```
80
84
 
85
+ An `email` attribute or method on your `Billable` model is required.
86
+
81
87
  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.
82
88
 
83
89
  ## Configuration
@@ -131,10 +137,15 @@ development:
131
137
  public_key: yyyy
132
138
  merchant_id: aaaa
133
139
  environment: sandbox
140
+ paddle:
141
+ vendor_id: xxxx
142
+ vendor_auth_code: yyyy
143
+ public_key_base64: MII...==
134
144
  ```
135
145
 
136
146
  For Stripe, you can also use the `STRIPE_PUBLIC_KEY`, `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
137
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.
138
149
 
139
150
  ### Generators
140
151
 
@@ -194,6 +205,8 @@ user.on_generic_trial? #=> true
194
205
 
195
206
  #### Creating a Charge
196
207
 
208
+ ##### Stripe and Braintree
209
+
197
210
  ```ruby
198
211
  user = User.find_by(email: 'michael@bluthcompany.co')
199
212
 
@@ -217,8 +230,23 @@ different currencies, etc.
217
230
  On failure, a `Pay::Error` will be raised with details about the payment
218
231
  failure.
219
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
+
220
246
  #### Creating a Subscription
221
247
 
248
+ ##### Stripe and Braintree
249
+
222
250
  ```ruby
223
251
  user = User.find_by(email: 'michael@bluthcompany.co')
224
252
 
@@ -237,20 +265,60 @@ def subscribe(name: 'default', plan: 'default', **options)
237
265
  end
238
266
  ```
239
267
 
240
- ##### Name
268
+ For example, you can pass the `quantity` option to subscribe to a plan with for per-seat pricing.
269
+
270
+ ```ruby
271
+
272
+ user.subscribe(name: "default", plan: "default", quantity: 3)
273
+ ```
274
+
275
+ ###### Name
241
276
 
242
277
  Name is an internally used name for the subscription.
243
278
 
244
- ##### Plan
279
+ ###### Plan
245
280
 
246
- Plan is the plan ID from the payment processor.
281
+ Plan is the plan ID or price ID from the payment processor. For example: `plan_xxxxx` or `price_xxxxx`
247
282
 
248
- ##### Options
283
+ ###### Options
249
284
 
250
285
  By default, the trial specified on the subscription will be used.
251
286
 
252
287
  `trial_period_days: 30` can be set to override and a trial to the subscription. This works the same for Braintree and Stripe.
253
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
+
254
322
  #### Retrieving a Subscription from the Database
255
323
 
256
324
  ```ruby
@@ -307,26 +375,45 @@ Plan is the plan ID from the payment processor.
307
375
 
308
376
  #### Retrieving a Payment Processor Account
309
377
 
378
+ ##### Stripe and Braintree
379
+
310
380
  ```ruby
311
381
  user = User.find_by(email: 'george.michael@bluthcompany.co')
312
382
 
313
383
  user.customer #> Stripe or Braintree customer account
314
384
  ```
315
385
 
386
+ ##### Paddle
387
+
388
+ It is currently not possible to retrieve a payment processor account through the API.
389
+
316
390
  #### Updating a Customer's Credit Card
317
391
 
392
+ ##### Stripe and Braintree
393
+
318
394
  ```ruby
319
395
  user = User.find_by(email: 'tobias@bluthcompany.co')
320
396
 
321
397
  user.update_card('payment_method_id')
322
398
  ```
323
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
+
324
411
  #### Retrieving a Customer's Subscription from the Processor
325
412
 
326
413
  ```ruby
327
414
  user = User.find_by(email: 'lucille@bluthcompany.co')
328
415
 
329
- user.processor_subscription(subscription_id) #=> Stripe or Braintree Subscription
416
+ user.processor_subscription(subscription_id) #=> Stripe, Braintree or Paddle Subscription
330
417
  ```
331
418
 
332
419
  ## Subscription API
@@ -363,14 +450,31 @@ user = User.find_by(email: 'carl.weathers@bluthcompany.co')
363
450
  user.subscription.active? #=> true or false
364
451
  ```
365
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
+
366
461
  #### Cancel a Subscription (At End of Billing Cycle)
367
462
 
463
+ ##### Stripe, Braintree and Paddle
464
+
368
465
  ```ruby
369
466
  user = User.find_by(email: 'oscar@bluthcompany.co')
370
467
 
371
468
  user.subscription.cancel
372
469
  ```
373
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
+
374
478
  #### Cancel a Subscription Immediately
375
479
 
376
480
  ```ruby
@@ -379,6 +483,16 @@ user = User.find_by(email: 'annyong@bluthcompany.co')
379
483
  user.subscription.cancel_now!
380
484
  ```
381
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
+
382
496
  #### Swap a Subscription to another Plan
383
497
 
384
498
  ```ruby
@@ -387,7 +501,17 @@ user = User.find_by(email: 'steve.holt@bluthcompany.co')
387
501
  user.subscription.swap("yearly")
388
502
  ```
389
503
 
390
- #### Resume a Subscription on a Grace Period
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)
391
515
 
392
516
  ```ruby
393
517
  user = User.find_by(email: 'steve.holt@bluthcompany.co')
@@ -464,8 +588,8 @@ config.routes_path = '/secret-webhook-path'
464
588
 
465
589
  ## Payment Providers
466
590
 
467
- We support both Stripe and Braintree and make our best attempt to
468
- standardize the two. They function differently so keep that in mind if
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
469
593
  you plan on doing more complex payments. It would be best to stick with
470
594
  a single payment provider in that case so you don't run into
471
595
  discrepancies.
@@ -480,7 +604,22 @@ development:
480
604
  merchant_id: zzzz
481
605
  environment: sandbox
482
606
  ```
607
+ #### Paddle
483
608
 
609
+ ```yaml
610
+ paddle:
611
+ vendor_id: xxxx
612
+ vendor_auth_code: yyyy
613
+ public_key_base64: MII...==
614
+ ```
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
+ ```
484
623
  #### Stripe
485
624
 
486
625
  You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
@@ -497,6 +636,20 @@ You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environmen
497
636
 
498
637
  **To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
499
638
 
639
+ You need the following event types to trigger the webhook:
640
+
641
+ ```
642
+ customer.subscription.updated
643
+ customer.subscription.deleted
644
+ customer.subscription.created
645
+ payment_method.updated
646
+ invoice.payment_action_required
647
+ customer.updated
648
+ customer.deleted
649
+ charge.succeeded
650
+ charge.refunded
651
+ ```
652
+
500
653
  ##### Strong Customer Authentication (SCA)
501
654
 
502
655
  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.
@@ -511,9 +664,7 @@ correctly for SCA payments.
511
664
  stripe listen --forward-to localhost:3000/pay/webhooks/stripe
512
665
  ```
513
666
 
514
- 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).
515
-
516
- The Javascript will now need to use createPaymentMethod instead of createToken. https://stripe.com/docs/js/payment_intents/create_payment_method
667
+ 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).
517
668
 
518
669
  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.
519
670
 
@@ -545,7 +696,10 @@ If you have an issue you'd like to submit, please do so using the issue tracker
545
696
 
546
697
  If you'd like to open a PR please make sure the following things pass:
547
698
 
548
- - `rake test`
699
+ ```ruby
700
+ bin/rails db:test:prepare
701
+ bin/rails test
702
+ ```
549
703
 
550
704
  ## License
551
705
 
data/Rakefile CHANGED
@@ -1,33 +1,31 @@
1
1
  begin
2
- require 'bundler/setup'
2
+ require "bundler/setup"
3
3
  rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
4
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
5
  end
6
6
 
7
- require 'rdoc/task'
7
+ require "bundler/gem_tasks"
8
+
9
+ require "rdoc/task"
8
10
 
9
11
  RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'Pay'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
12
+ rdoc.rdoc_dir = "rdoc"
13
+ rdoc.title = "Pay"
14
+ rdoc.options << "--line-numbers"
15
+ rdoc.rdoc_files.include("README.md")
16
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
17
  end
16
18
 
17
19
  APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
- load 'rails/tasks/engine.rake'
19
-
20
- load 'rails/tasks/statistics.rake'
20
+ load "rails/tasks/engine.rake"
21
21
 
22
- unless Rails.env.test?
23
- require "bundler/gem_tasks"
24
- end
22
+ load "rails/tasks/statistics.rake"
25
23
 
26
- require 'rake/testtask'
24
+ require "rake/testtask"
27
25
 
28
26
  Rake::TestTask.new(:test) do |t|
29
- t.libs << 'test'
30
- t.pattern = 'test/**/*_test.rb'
27
+ t.libs << "test"
28
+ t.pattern = "test/**/*_test.rb"
31
29
  t.verbose = false
32
30
  end
33
31
 
@@ -1,6 +1,9 @@
1
1
  module Pay
2
2
  class PaymentsController < ApplicationController
3
+ layout "pay/application"
4
+
3
5
  def show
6
+ @redirect_to = params[:back].presence || root_path
4
7
  @payment = Payment.from_id(params[:id])
5
8
  end
6
9
  end
@@ -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
@@ -44,8 +44,8 @@ module Pay
44
44
  private
45
45
 
46
46
  def to(user)
47
- if user.respond_to?(:name)
48
- "#{user.name} <#{user.email}>"
47
+ if user.respond_to?(:customer_name)
48
+ "#{user.customer_name} <#{user.email}>"
49
49
  else
50
50
  user.email
51
51
  end