pay 2.2.2 → 2.4.3

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +142 -9
  3. data/Rakefile +2 -4
  4. data/app/controllers/pay/payments_controller.rb +2 -0
  5. data/app/controllers/pay/webhooks/braintree_controller.rb +1 -1
  6. data/app/controllers/pay/webhooks/paddle_controller.rb +36 -0
  7. data/app/mailers/pay/user_mailer.rb +14 -35
  8. data/app/models/pay/application_record.rb +6 -1
  9. data/app/models/pay/charge.rb +7 -0
  10. data/app/models/pay/subscription.rb +23 -8
  11. data/app/views/pay/user_mailer/payment_action_required.html.erb +1 -1
  12. data/app/views/pay/user_mailer/receipt.html.erb +6 -6
  13. data/app/views/pay/user_mailer/refund.html.erb +6 -6
  14. data/app/views/pay/user_mailer/subscription_renewing.html.erb +1 -1
  15. data/config/locales/en.yml +137 -0
  16. data/config/routes.rb +1 -0
  17. data/db/migrate/20200603134434_add_data_to_pay_models.rb +22 -0
  18. data/lib/pay.rb +9 -41
  19. data/lib/pay/billable.rb +14 -9
  20. data/lib/pay/braintree/billable.rb +21 -15
  21. data/lib/pay/braintree/charge.rb +7 -3
  22. data/lib/pay/braintree/subscription.rb +22 -8
  23. data/lib/pay/engine.rb +7 -0
  24. data/lib/pay/errors.rb +73 -0
  25. data/lib/pay/paddle.rb +38 -0
  26. data/lib/pay/paddle/billable.rb +95 -0
  27. data/lib/pay/paddle/charge.rb +39 -0
  28. data/lib/pay/paddle/subscription.rb +68 -0
  29. data/lib/pay/paddle/webhooks.rb +1 -0
  30. data/lib/pay/paddle/webhooks/signature_verifier.rb +115 -0
  31. data/lib/pay/paddle/webhooks/subscription_cancelled.rb +18 -0
  32. data/lib/pay/paddle/webhooks/subscription_created.rb +59 -0
  33. data/lib/pay/paddle/webhooks/subscription_payment_refunded.rb +21 -0
  34. data/lib/pay/paddle/webhooks/subscription_payment_succeeded.rb +43 -0
  35. data/lib/pay/paddle/webhooks/subscription_updated.rb +37 -0
  36. data/lib/pay/receipts.rb +6 -6
  37. data/lib/pay/stripe.rb +3 -0
  38. data/lib/pay/stripe/billable.rb +10 -4
  39. data/lib/pay/stripe/charge.rb +6 -2
  40. data/lib/pay/stripe/subscription.rb +21 -7
  41. data/lib/pay/stripe/webhooks/charge_refunded.rb +2 -2
  42. data/lib/pay/stripe/webhooks/charge_succeeded.rb +7 -7
  43. data/lib/pay/stripe/webhooks/payment_action_required.rb +7 -8
  44. data/lib/pay/stripe/webhooks/subscription_created.rb +1 -1
  45. data/lib/pay/stripe/webhooks/subscription_renewing.rb +4 -3
  46. data/lib/pay/version.rb +1 -1
  47. metadata +32 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b0dc3430e6f89e4c1516a122bde97173ee5bdb1bfa835bb968b36083b633c4f
4
- data.tar.gz: 0dbd1c62002fa67fbf9e78591f52199c9322e6d52868fb443e640f93dd2863d0
3
+ metadata.gz: 8355ed0ced2d06b8b5a0d7128c982f7f407c3c75302e7d58b0da9c37986bf05a
4
+ data.tar.gz: 633734b4679a2e076bec4441ac77054bbcf67feef99147ff6717e9412c6b7394
5
5
  SHA512:
6
- metadata.gz: d6e108e2377cfabdd8da381f225094d4f8f2e0fb25ca28dbadf0cfbfaad1bbafbbb1f4613c92ac0bd8dc0d87c4de14b21d32004ab31ab572588bef4926746f92
7
- data.tar.gz: 68ebaeed4f068419a51860d853bd914ba6c5076e590bac85005feb431a404bb7ea8c3fbbe113876e0f6ec56ad539d43e698504b99aa5951bc76894d18961f8c6
6
+ metadata.gz: ed6797d1a8f9c586ad516d0b94756103f99bc780334c1a62844785658992ee64aecfad2d8b112c8317c6565ae83afe52571da827a57709d730608799be9f2681
7
+ data.tar.gz: c30ed21fa64d092326981777dfada4d6a95e51c60ce5bf255155171daed519b7fcf1891f609c25dc688e68e96bc5cf0f890a3b7675e123a52544894d65b912d0
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
  ```
@@ -99,6 +103,9 @@ Pay.setup do |config|
99
103
 
100
104
  config.send_emails = true
101
105
 
106
+ config.default_product_name = "default"
107
+ config.default_plan_name = "default"
108
+
102
109
  config.automount_routes = true
103
110
  config.routes_path = "/pay" # Only when automount_routes is true
104
111
  end
@@ -133,10 +140,15 @@ development:
133
140
  public_key: yyyy
134
141
  merchant_id: aaaa
135
142
  environment: sandbox
143
+ paddle:
144
+ vendor_id: xxxx
145
+ vendor_auth_code: yyyy
146
+ public_key_base64: MII...==
136
147
  ```
137
148
 
138
149
  For Stripe, you can also use the `STRIPE_PUBLIC_KEY`, `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
139
150
  For Braintree, you can also use `BRAINTREE_MERCHANT_ID`, `BRAINTREE_PUBLIC_KEY`, `BRAINTREE_PRIVATE_KEY`, and `BRAINTREE_ENVIRONMENT` environment variables.
151
+ For Paddle, you can also use `PADDLE_VENDOR_ID`, `PADDLE_VENDOR_AUTH_CODE` and `PADDLE_PUBLIC_KEY_BASE64` environment variables.
140
152
 
141
153
  ### Generators
142
154
 
@@ -196,6 +208,8 @@ user.on_generic_trial? #=> true
196
208
 
197
209
  #### Creating a Charge
198
210
 
211
+ ##### Stripe and Braintree
212
+
199
213
  ```ruby
200
214
  user = User.find_by(email: 'michael@bluthcompany.co')
201
215
 
@@ -219,8 +233,23 @@ different currencies, etc.
219
233
  On failure, a `Pay::Error` will be raised with details about the payment
220
234
  failure.
221
235
 
236
+ ##### Paddle
237
+ It is only possible to create immediate one-time charges on top of an existing subscription.
238
+
239
+ ```ruby
240
+ user = User.find_by(email: 'michael@bluthcompany.co')
241
+
242
+ user.processor = 'paddle'
243
+ user.charge(1500, {charge_name: "Test"}) # $15.00 USD
244
+
245
+ ```
246
+
247
+ An existing subscription and a charge name are required.
248
+
222
249
  #### Creating a Subscription
223
250
 
251
+ ##### Stripe and Braintree
252
+
224
253
  ```ruby
225
254
  user = User.find_by(email: 'michael@bluthcompany.co')
226
255
 
@@ -234,7 +263,7 @@ A `card_token` must be provided as an attribute.
234
263
  The subscribe method has three optional arguments with default values.
235
264
 
236
265
  ```ruby
237
- def subscribe(name: 'default', plan: 'default', **options)
266
+ def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
238
267
  ...
239
268
  end
240
269
  ```
@@ -243,23 +272,56 @@ For example, you can pass the `quantity` option to subscribe to a plan with for
243
272
 
244
273
  ```ruby
245
274
 
246
- user.subscribe(name: "default", plan: "default", quantity: 3)
275
+ user.subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, quantity: 3)
247
276
  ```
248
277
 
249
- ##### Name
278
+ ###### Name
250
279
 
251
280
  Name is an internally used name for the subscription.
252
281
 
253
- ##### Plan
282
+ ###### Plan
254
283
 
255
284
  Plan is the plan ID or price ID from the payment processor. For example: `plan_xxxxx` or `price_xxxxx`
256
285
 
257
- ##### Options
286
+ ###### Options
258
287
 
259
288
  By default, the trial specified on the subscription will be used.
260
289
 
261
290
  `trial_period_days: 30` can be set to override and a trial to the subscription. This works the same for Braintree and Stripe.
262
291
 
292
+ ##### Paddle
293
+ 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.
294
+
295
+ 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).
296
+
297
+ Javascript Checkout:
298
+ ```javascript
299
+ Paddle.Checkout.open({
300
+ product: 12345,
301
+ passthrough: "<%= Pay::Paddle.passthrough(owner: current_user) %>"
302
+ });
303
+ ```
304
+
305
+ Paddle Button Checkout:
306
+ ```html
307
+ <a href="#!" class="paddle_button" data-product="12345" data-email="<%= current_user.email %>" data-passthrough="<%= Pay::Paddle.passthrough(owner: current_user) %>"
308
+ ```
309
+
310
+ ###### Passthrough
311
+
312
+ Pay providers a helper method for generating the passthrough JSON object to associate the purchase with the correct Rails model.
313
+
314
+ ```ruby
315
+ Pay::Paddle.passthrough(owner: current_user, foo: :bar)
316
+ #=> { owner_sgid: "xxxxxxxx", foo: "bar" }
317
+
318
+ # To generate manually without the helper
319
+ #=> { owner_sgid: current_user.to_sgid.to_s, foo: "bar" }.to_json
320
+ ```
321
+
322
+ Pay parses the passthrough JSON string and verifies the `owner_sgid` hash to match the webhook with the correct billable record.
323
+ The passthrough parameter `owner_sgid` is only required for creating a subscription.
324
+
263
325
  #### Retrieving a Subscription from the Database
264
326
 
265
327
  ```ruby
@@ -316,26 +378,45 @@ Plan is the plan ID from the payment processor.
316
378
 
317
379
  #### Retrieving a Payment Processor Account
318
380
 
381
+ ##### Stripe and Braintree
382
+
319
383
  ```ruby
320
384
  user = User.find_by(email: 'george.michael@bluthcompany.co')
321
385
 
322
386
  user.customer #> Stripe or Braintree customer account
323
387
  ```
324
388
 
389
+ ##### Paddle
390
+
391
+ It is currently not possible to retrieve a payment processor account through the API.
392
+
325
393
  #### Updating a Customer's Credit Card
326
394
 
395
+ ##### Stripe and Braintree
396
+
327
397
  ```ruby
328
398
  user = User.find_by(email: 'tobias@bluthcompany.co')
329
399
 
330
400
  user.update_card('payment_method_id')
331
401
  ```
332
402
 
403
+ ##### Paddle
404
+
405
+ 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.
406
+ ```ruby
407
+ user = User.find_by(email: 'tobias@bluthcompany.co')
408
+
409
+ user.subscription.paddle_update_url
410
+ ```
411
+
412
+
413
+
333
414
  #### Retrieving a Customer's Subscription from the Processor
334
415
 
335
416
  ```ruby
336
417
  user = User.find_by(email: 'lucille@bluthcompany.co')
337
418
 
338
- user.processor_subscription(subscription_id) #=> Stripe or Braintree Subscription
419
+ user.processor_subscription(subscription_id) #=> Stripe, Braintree or Paddle Subscription
339
420
  ```
340
421
 
341
422
  ## Subscription API
@@ -372,14 +453,31 @@ user = User.find_by(email: 'carl.weathers@bluthcompany.co')
372
453
  user.subscription.active? #=> true or false
373
454
  ```
374
455
 
456
+ #### Checking to See If a Subscription Is Paused
457
+
458
+ ```ruby
459
+ user = User.find_by(email: 'carl.weathers@bluthcompany.co')
460
+
461
+ user.subscription.paused? #=> true or false
462
+ ```
463
+
375
464
  #### Cancel a Subscription (At End of Billing Cycle)
376
465
 
466
+ ##### Stripe, Braintree and Paddle
467
+
377
468
  ```ruby
378
469
  user = User.find_by(email: 'oscar@bluthcompany.co')
379
470
 
380
471
  user.subscription.cancel
381
472
  ```
382
473
 
474
+ ##### Paddle
475
+ 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.
476
+
477
+ ```ruby
478
+ user.subscription.paddle_cancel_url
479
+ ```
480
+
383
481
  #### Cancel a Subscription Immediately
384
482
 
385
483
  ```ruby
@@ -388,6 +486,16 @@ user = User.find_by(email: 'annyong@bluthcompany.co')
388
486
  user.subscription.cancel_now!
389
487
  ```
390
488
 
489
+ #### Pause a Subscription
490
+
491
+ ##### Paddle
492
+
493
+ ```ruby
494
+ user = User.find_by(email: 'oscar@bluthcompany.co')
495
+
496
+ user.subscription.pause
497
+ ```
498
+
391
499
  #### Swap a Subscription to another Plan
392
500
 
393
501
  ```ruby
@@ -396,7 +504,17 @@ user = User.find_by(email: 'steve.holt@bluthcompany.co')
396
504
  user.subscription.swap("yearly")
397
505
  ```
398
506
 
399
- #### Resume a Subscription on a Grace Period
507
+ #### Resume a Subscription
508
+
509
+ ##### Stripe or Braintree Subscription (on Grace Period)
510
+
511
+ ```ruby
512
+ user = User.find_by(email: 'steve.holt@bluthcompany.co')
513
+
514
+ user.subscription.resume
515
+ ```
516
+
517
+ ##### Paddle (Paused)
400
518
 
401
519
  ```ruby
402
520
  user = User.find_by(email: 'steve.holt@bluthcompany.co')
@@ -473,8 +591,8 @@ config.routes_path = '/secret-webhook-path'
473
591
 
474
592
  ## Payment Providers
475
593
 
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
594
+ We support Stripe, Braintree and Paddle and make our best attempt to
595
+ standardize the three. They function differently so keep that in mind if
478
596
  you plan on doing more complex payments. It would be best to stick with
479
597
  a single payment provider in that case so you don't run into
480
598
  discrepancies.
@@ -489,7 +607,22 @@ development:
489
607
  merchant_id: zzzz
490
608
  environment: sandbox
491
609
  ```
610
+ #### Paddle
611
+
612
+ ```yaml
613
+ paddle:
614
+ vendor_id: xxxx
615
+ vendor_auth_code: yyyy
616
+ public_key_base64: MII...==
617
+ ```
492
618
 
619
+ Paddle receipts can be retrieved by a charge receipt URL.
620
+ ```ruby
621
+ user = User.find_by(email: 'annyong@bluthcompany.co')
622
+
623
+ charge = user.charges.first
624
+ charge.paddle_receipt_url
625
+ ```
493
626
  #### Stripe
494
627
 
495
628
  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])
@@ -33,7 +33,7 @@ module Pay
33
33
  charge = billable.save_braintree_transaction(subscription.transactions.first)
34
34
 
35
35
  if Pay.send_emails
36
- Pay::UserMailer.receipt(billable, charge).deliver_later
36
+ Pay::UserMailer.with(billable: billable, charge: charge).receipt.deliver_later
37
37
  end
38
38
  end
39
39
 
@@ -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,53 +1,32 @@
1
1
  module Pay
2
2
  class UserMailer < ApplicationMailer
3
- def receipt(user, charge)
4
- @user, @charge = user, charge
5
-
6
- if charge.respond_to? :receipt
7
- attachments[charge.filename] = charge.receipt
3
+ def receipt
4
+ if params[:charge].respond_to? :receipt
5
+ attachments[params[:charge].filename] = params[:charge].receipt
8
6
  end
9
7
 
10
- mail(
11
- to: to(user),
12
- subject: Pay.email_receipt_subject
13
- )
8
+ mail to: to
14
9
  end
15
10
 
16
- def refund(user, charge)
17
- @user, @charge = user, charge
18
-
19
- mail(
20
- to: to(user),
21
- subject: Pay.email_refund_subject
22
- )
11
+ def refund
12
+ mail to: to
23
13
  end
24
14
 
25
- def subscription_renewing(user, subscription)
26
- @user, @subscription = user, subscription
27
-
28
- mail(
29
- to: to(user),
30
- subject: Pay.email_renewing_subject
31
- )
15
+ def subscription_renewing
16
+ mail to: to
32
17
  end
33
18
 
34
- def payment_action_required(user, payment_intent_id, subscription)
35
- payment = Payment.from_id(payment_intent_id)
36
- @user, @payment, @subscription = user, payment, subscription
37
-
38
- mail(
39
- to: to(user),
40
- subject: Pay.payment_action_required_subject
41
- )
19
+ def payment_action_required
20
+ mail to: to
42
21
  end
43
22
 
44
23
  private
45
24
 
46
- def to(user)
47
- if user.respond_to?(:customer_name)
48
- "#{user.customer_name} <#{user.email}>"
25
+ def to
26
+ if params[:billable].respond_to?(:customer_name)
27
+ "#{params[:billable].customer_name} <#{params[:billable].email}>"
49
28
  else
50
- user.email
29
+ params[:billable].email
51
30
  end
52
31
  end
53
32
  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