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.

Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +137 -7
  3. data/Rakefile +2 -4
  4. data/app/controllers/pay/payments_controller.rb +2 -0
  5. data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
  6. data/app/models/pay/application_record.rb +6 -1
  7. data/app/models/pay/charge.rb +7 -0
  8. data/app/models/pay/subscription.rb +24 -3
  9. data/config/routes.rb +1 -0
  10. data/db/migrate/20200603134434_add_data_to_pay_models.rb +17 -0
  11. data/lib/pay.rb +3 -0
  12. data/lib/pay/billable.rb +5 -0
  13. data/lib/pay/braintree/billable.rb +10 -4
  14. data/lib/pay/braintree/charge.rb +4 -0
  15. data/lib/pay/braintree/subscription.rb +6 -0
  16. data/lib/pay/engine.rb +7 -0
  17. data/lib/pay/paddle.rb +38 -0
  18. data/lib/pay/paddle/billable.rb +66 -0
  19. data/lib/pay/paddle/charge.rb +39 -0
  20. data/lib/pay/paddle/subscription.rb +59 -0
  21. data/lib/pay/paddle/webhooks.rb +1 -0
  22. data/lib/pay/paddle/webhooks/signature_verifier.rb +115 -0
  23. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +18 -0
  24. data/lib/pay/paddle/webhooks/subscription_created.rb +59 -0
  25. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +21 -0
  26. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +64 -0
  27. data/lib/pay/paddle/webhooks/subscription_updated.rb +34 -0
  28. data/lib/pay/stripe/billable.rb +6 -0
  29. data/lib/pay/stripe/charge.rb +4 -0
  30. data/lib/pay/stripe/subscription.rb +6 -0
  31. data/lib/pay/stripe/webhooks/charge_succeeded.rb +7 -7
  32. data/lib/pay/stripe/webhooks/payment_action_required.rb +7 -8
  33. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
  34. data/lib/pay/version.rb +1 -1
  35. metadata +59 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b0dc3430e6f89e4c1516a122bde97173ee5bdb1bfa835bb968b36083b633c4f
4
- data.tar.gz: 0dbd1c62002fa67fbf9e78591f52199c9322e6d52868fb443e640f93dd2863d0
3
+ metadata.gz: cd6aa7121588fb74663d51c6fdb21d0a67b196cc1f125921ae208ad65758fb6c
4
+ data.tar.gz: f15391bc0601b3027f19ec2c5111103fd0d739b24a48366e2f3c67489c2b99c3
5
5
  SHA512:
6
- metadata.gz: d6e108e2377cfabdd8da381f225094d4f8f2e0fb25ca28dbadf0cfbfaad1bbafbbb1f4613c92ac0bd8dc0d87c4de14b21d32004ab31ab572588bef4926746f92
7
- data.tar.gz: 68ebaeed4f068419a51860d853bd914ba6c5076e590bac85005feb431a404bb7ea8c3fbbe113876e0f6ec56ad539d43e698504b99aa5951bc76894d18961f8c6
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
- ##### Name
275
+ ###### Name
250
276
 
251
277
  Name is an internally used name for the subscription.
252
278
 
253
- ##### Plan
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
- ##### Options
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 Braintree Subscription
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 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)
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 both Stripe and Braintree and make our best attempt to
477
- 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
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|
@@ -1,5 +1,7 @@
1
1
  module Pay
2
2
  class PaymentsController < ApplicationController
3
+ layout "pay/application"
4
+
3
5
  def show
4
6
  @redirect_to = params[:back].presence || root_path
5
7
  @payment = Payment.from_id(params[:id])
@@ -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 < 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
@@ -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
- raise StandardError,
80
- "You can only resume subscriptions within their grace period."
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")
@@ -4,4 +4,5 @@ Pay::Engine.routes.draw do
4
4
  resources :payments, only: [:show], module: :pay
5
5
  post "webhooks/stripe", to: "stripe_event/webhook#event"
6
6
  post "webhooks/braintree", to: "pay/webhooks/braintree#create"
7
+ post "webhooks/paddle", to: "pay/webhooks/paddle#create"
7
8
  end
@@ -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