pay 2.1.3 → 2.3.1

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 +161 -14
  3. data/Rakefile +2 -4
  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 +1 -1
  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 +5 -2
  18. data/lib/pay/billable.rb +7 -2
  19. data/lib/pay/braintree.rb +1 -1
  20. data/lib/pay/braintree/billable.rb +14 -8
  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 +11 -3
  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 +59 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 883c55bf9abc32584d98fe56c9a710e92906bebc5322b1ec9ed9dadf92fcd9dc
4
- data.tar.gz: da1849f216e7d5e3a8365b3427a4bebf914d51e1c41e9736bc3b8df726f58ef7
3
+ metadata.gz: 938efc72579bf8d24477a0e55060280a8e2d2df139860cdfe84dcc715f0eeb23
4
+ data.tar.gz: 3f00c21f027beae9a68d9da5228d9fa0165eeb2153d62e48544702565e0834d7
5
5
  SHA512:
6
- metadata.gz: ab3d62146de9a533a9598e0d87970a019f8b23646a92110c3d62ef64ec8385c2158c7435c7b255648faa4740360634f59d5b3a16f95c44347e7aeedc004f9674
7
- data.tar.gz: 598d32db66f8fd5b43cfb27766826223d4a7dd80c73204e23e6a5bb36783775f8c521d8682e4c466f059e6f71541b8c799cfed5bab1330fc4992901c06cb62cc
6
+ metadata.gz: 5d2ef6252a25049649fe460553766aa61122c2d3541b7eb6b4197ca4614c9110bcf9243fd5aa08ab90d32b2a224317dcf2e9275a8cd918bee4c0b8146703fd37
7
+ data.tar.gz: '0992d392de15e633d5dcd190f9ed25bd356cf1a8da548e14b2560314aafa59593e5fcb64a68d70d16d5b58a6b6df0df55eba7259bb88c92166439bc61974af0e'
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
 
@@ -244,20 +272,53 @@ For example, you can pass the `quantity` option to subscribe to a plan with for
244
272
  user.subscribe(name: "default", plan: "default", quantity: 3)
245
273
  ```
246
274
 
247
- ##### Name
275
+ ###### Name
248
276
 
249
277
  Name is an internally used name for the subscription.
250
278
 
251
- ##### Plan
279
+ ###### Plan
252
280
 
253
- 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`
254
282
 
255
- ##### Options
283
+ ###### Options
256
284
 
257
285
  By default, the trial specified on the subscription will be used.
258
286
 
259
287
  `trial_period_days: 30` can be set to override and a trial to the subscription. This works the same for Braintree and Stripe.
260
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
+
261
322
  #### Retrieving a Subscription from the Database
262
323
 
263
324
  ```ruby
@@ -314,26 +375,45 @@ Plan is the plan ID from the payment processor.
314
375
 
315
376
  #### Retrieving a Payment Processor Account
316
377
 
378
+ ##### Stripe and Braintree
379
+
317
380
  ```ruby
318
381
  user = User.find_by(email: 'george.michael@bluthcompany.co')
319
382
 
320
383
  user.customer #> Stripe or Braintree customer account
321
384
  ```
322
385
 
386
+ ##### Paddle
387
+
388
+ It is currently not possible to retrieve a payment processor account through the API.
389
+
323
390
  #### Updating a Customer's Credit Card
324
391
 
392
+ ##### Stripe and Braintree
393
+
325
394
  ```ruby
326
395
  user = User.find_by(email: 'tobias@bluthcompany.co')
327
396
 
328
397
  user.update_card('payment_method_id')
329
398
  ```
330
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
+
331
411
  #### Retrieving a Customer's Subscription from the Processor
332
412
 
333
413
  ```ruby
334
414
  user = User.find_by(email: 'lucille@bluthcompany.co')
335
415
 
336
- user.processor_subscription(subscription_id) #=> Stripe or Braintree Subscription
416
+ user.processor_subscription(subscription_id) #=> Stripe, Braintree or Paddle Subscription
337
417
  ```
338
418
 
339
419
  ## Subscription API
@@ -370,14 +450,31 @@ user = User.find_by(email: 'carl.weathers@bluthcompany.co')
370
450
  user.subscription.active? #=> true or false
371
451
  ```
372
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
+
373
461
  #### Cancel a Subscription (At End of Billing Cycle)
374
462
 
463
+ ##### Stripe, Braintree and Paddle
464
+
375
465
  ```ruby
376
466
  user = User.find_by(email: 'oscar@bluthcompany.co')
377
467
 
378
468
  user.subscription.cancel
379
469
  ```
380
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
+
381
478
  #### Cancel a Subscription Immediately
382
479
 
383
480
  ```ruby
@@ -386,6 +483,16 @@ user = User.find_by(email: 'annyong@bluthcompany.co')
386
483
  user.subscription.cancel_now!
387
484
  ```
388
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
+
389
496
  #### Swap a Subscription to another Plan
390
497
 
391
498
  ```ruby
@@ -394,7 +501,17 @@ user = User.find_by(email: 'steve.holt@bluthcompany.co')
394
501
  user.subscription.swap("yearly")
395
502
  ```
396
503
 
397
- #### 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)
398
515
 
399
516
  ```ruby
400
517
  user = User.find_by(email: 'steve.holt@bluthcompany.co')
@@ -471,8 +588,8 @@ config.routes_path = '/secret-webhook-path'
471
588
 
472
589
  ## Payment Providers
473
590
 
474
- We support both Stripe and Braintree and make our best attempt to
475
- standardize the two. They function differently so keep that in mind if
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
476
593
  you plan on doing more complex payments. It would be best to stick with
477
594
  a single payment provider in that case so you don't run into
478
595
  discrepancies.
@@ -487,7 +604,22 @@ development:
487
604
  merchant_id: zzzz
488
605
  environment: sandbox
489
606
  ```
607
+ #### Paddle
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')
490
619
 
620
+ charge = user.charges.first
621
+ charge.paddle_receipt_url
622
+ ```
491
623
  #### Stripe
492
624
 
493
625
  You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
@@ -504,6 +636,20 @@ You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environmen
504
636
 
505
637
  **To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
506
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
+
507
653
  ##### Strong Customer Authentication (SCA)
508
654
 
509
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.
@@ -518,9 +664,7 @@ correctly for SCA payments.
518
664
  stripe listen --forward-to localhost:3000/pay/webhooks/stripe
519
665
  ```
520
666
 
521
- You should use `stripe.handleCardSetup` on the client to collect card information anytime you want to save the card and charge them later (adding a card, then charging them on the next page for example). Use `stripe.handleCardPayment` if you'd like to charge the customer immediately (think checking out of a shopping cart).
522
-
523
- The Javascript will now need to use createPaymentMethod instead of createToken. https://stripe.com/docs/js/payment_intents/create_payment_method
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).
524
668
 
525
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.
526
670
 
@@ -552,7 +696,10 @@ If you have an issue you'd like to submit, please do so using the issue tracker
552
696
 
553
697
  If you'd like to open a PR please make sure the following things pass:
554
698
 
555
- - `rake test`
699
+ ```ruby
700
+ bin/rails db:test:prepare
701
+ bin/rails test
702
+ ```
556
703
 
557
704
  ## License
558
705
 
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|
@@ -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
@@ -37,7 +37,7 @@ module Pay
37
37
 
38
38
  mail(
39
39
  to: to(user),
40
- subject: Pay.payment_action_required_subject
40
+ subject: Pay.email_action_required_subject
41
41
  )
42
42
  end
43
43
 
@@ -1,5 +1,10 @@
1
1
  module Pay
2
- class ApplicationRecord < ActiveRecord::Base
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
@@ -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