pay 1.0.2 → 2.0.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +99 -31
  3. data/Rakefile +19 -19
  4. data/app/controllers/pay/payments_controller.rb +7 -0
  5. data/app/controllers/pay/webhooks/braintree_controller.rb +19 -6
  6. data/app/mailers/pay/application_mailer.rb +2 -2
  7. data/app/mailers/pay/user_mailer.rb +12 -0
  8. data/app/models/pay/subscription.rb +41 -10
  9. data/app/views/layouts/pay/application.html.erb +11 -5
  10. data/app/views/pay/payments/show.html.erb +134 -0
  11. data/app/views/pay/user_mailer/payment_action_required.html.erb +6 -0
  12. data/config/locales/en.yml +17 -0
  13. data/config/routes.rb +3 -2
  14. data/db/migrate/20190816015720_add_status_to_subscriptions.rb +14 -0
  15. data/lib/generators/pay/email_views_generator.rb +3 -3
  16. data/lib/generators/pay/views_generator.rb +13 -0
  17. data/lib/pay/billable/sync_email.rb +3 -4
  18. data/lib/pay/billable.rb +25 -18
  19. data/lib/pay/braintree/billable.rb +88 -82
  20. data/lib/pay/braintree/charge.rb +0 -2
  21. data/lib/pay/braintree/subscription.rb +74 -72
  22. data/lib/pay/braintree.rb +6 -6
  23. data/lib/pay/engine.rb +10 -10
  24. data/lib/pay/env.rb +0 -1
  25. data/lib/pay/payment.rb +52 -0
  26. data/lib/pay/receipts.rb +7 -7
  27. data/lib/pay/stripe/billable.rb +68 -37
  28. data/lib/pay/stripe/charge.rb +0 -2
  29. data/lib/pay/stripe/subscription.rb +7 -6
  30. data/lib/pay/stripe/webhooks/charge_refunded.rb +0 -2
  31. data/lib/pay/stripe/webhooks/charge_succeeded.rb +9 -10
  32. data/lib/pay/stripe/webhooks/customer_deleted.rb +5 -7
  33. data/lib/pay/stripe/webhooks/customer_updated.rb +0 -2
  34. data/lib/pay/stripe/webhooks/payment_action_required.rb +27 -0
  35. data/lib/pay/stripe/webhooks/{source_deleted.rb → payment_method_updated.rb} +1 -3
  36. data/lib/pay/stripe/webhooks/subscription_created.rb +6 -11
  37. data/lib/pay/stripe/webhooks/subscription_deleted.rb +1 -3
  38. data/lib/pay/stripe/webhooks/subscription_renewing.rb +0 -2
  39. data/lib/pay/stripe/webhooks/subscription_updated.rb +13 -10
  40. data/lib/pay/stripe/webhooks.rb +17 -11
  41. data/lib/pay/stripe.rb +5 -5
  42. data/lib/pay/version.rb +1 -1
  43. data/lib/pay.rb +42 -15
  44. metadata +27 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bde9d6a59e8bb2433c706309b09737bf46039ebc02f271b99923658ca3c3953
4
- data.tar.gz: 0ef88d80eee2e59e4bcf6a52d8754cb602ea0aa54ba0818ac29361ac8cd3e06c
3
+ metadata.gz: 9665c51290c7d5fddc48d2c7a719f936907eb3a4c818c1bd886ddd0a01b3ac8f
4
+ data.tar.gz: 845efceadcd8e44a93228d30b82ec438e9d58842f683c16b97ebd44aef7b53bf
5
5
  SHA512:
6
- metadata.gz: 73714b5b94113338beab99427ea82378b0f7de541f414662f15977e3470bc3cd3fe5069276e4fdbacf9617ffd189cd38a5eb6dc5a9d2668daa99eec964eb1936
7
- data.tar.gz: 333b4d632b7cd1636928eabd29388542b92839ef6f9e2fedb2bd658536fd2ebc8a40a3ca7c41fb1a3974c2d3b9566f5e74da2a71a2106e36afce01b2ea8a83d9
6
+ metadata.gz: ff36eeb6ea85bb3d7d057b75534d3ce02fd0b193e3c6356d52d03c17f8108add95b961299849511a1b744df86d8c38f16fb47650a769721531f722b01682b823
7
+ data.tar.gz: 1cab7944c0c018079b5acb4f08e13c5ddaee3881a977a618f1e84730fc6bb24040b8f60ff2da924fd8957dea81ebc5208b8b0ef3cda7c9aa84c4a2819804b5be
data/README.md CHANGED
@@ -4,27 +4,27 @@
4
4
 
5
5
  ### Payments engine for Ruby on Rails
6
6
 
7
- [![Build Status](https://travis-ci.org/excid3/pay.svg?branch=master)](https://travis-ci.org/excid3/pay)
7
+ [![Build Status](https://github.com/excid3/pay/workflows/Tests/badge.svg)](https://github.com/excid3/pay/actions)
8
8
 
9
9
  Pay is a payments engine for Ruby on Rails 4.2 and higher.
10
10
 
11
11
  **Current Payment Providers**
12
12
 
13
- - Stripe (API version [2018-08-23](https://stripe.com/docs/upgrades#2018-08-23) or higher required)
13
+ - 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)
14
14
  - Braintree
15
15
 
16
16
  Want to add a new payment provider? Contributions are welcome and the instructions [are here](https://github.com/jasoncharnes/pay/wiki/New-Payment-Provider).
17
17
 
18
18
  ## Installation
19
19
 
20
- Add this line to your application's Gemfile:
20
+ Add these lines to your application's Gemfile:
21
21
 
22
22
  ```ruby
23
23
  gem 'pay'
24
24
 
25
25
  # To use Stripe, also include:
26
- gem 'stripe', '< 5.0', '>= 2.8'
27
- gem 'stripe_event', '~> 2.2'
26
+ gem 'stripe', '< 6.0', '>= 2.8'
27
+ gem 'stripe_event', '~> 2.3'
28
28
 
29
29
  # To use Braintree + PayPal, also include:
30
30
  gem 'braintree', '< 3.0', '>= 2.92.0'
@@ -36,18 +36,18 @@ gem 'receipts', '~> 0.2.2'
36
36
  And then execute:
37
37
 
38
38
  ```bash
39
- $ bundle
39
+ bundle
40
40
  ```
41
41
 
42
42
  Or install it yourself as:
43
43
 
44
44
  ```bash
45
- $ gem install pay
45
+ gem install pay
46
46
  ```
47
47
 
48
48
  ## Setup
49
49
 
50
- #### Migrations
50
+ ### Migrations
51
51
 
52
52
  This engine will create a subscription model and the neccessary migrations for the model you want to make "billable." The most common use case for the billable model is a User.
53
53
 
@@ -55,32 +55,51 @@ To add the migrations to your application, run the following migration:
55
55
 
56
56
  `$ bin/rails pay:install:migrations`
57
57
 
58
- This will install three migrations:
58
+ This will install four migrations:
59
59
 
60
60
  - db/migrate/create_subscriptions.pay.rb
61
61
  - db/migrate/add_fields_to_users.pay.rb
62
62
  - db/migrate/create_charges.pay.rb
63
+ - db/migrate/add_status_to_subscriptions.pay.rb
63
64
 
64
- #### The User Model
65
+ ### The Billable Module
65
66
 
66
- If you have a `User` model defined in `app/models/user.rb` Pay will add the fields it needs to the `users` table.
67
+ To enable payments for a model, you simply include the `Pay::Billable`
68
+ module in it. By default, we assume this is `User`.
67
69
 
68
- If you do not have a `User` model defined, Pay will create a `users` table and you will need to add `app/models/user.rb`.
70
+ If you'd like to use a different model, you can configure it in an
71
+ initializer:
69
72
 
70
- #### Non-User Model
73
+ ```ruby
74
+ Pay.setup do |config|
75
+ # Make the billable class the same name as your ActiveRecord model
76
+ config.billable_class = "Team"
71
77
 
72
- If you need to use a model other than `User`, check out the [wiki page](https://github.com/jasoncharnes/pay/wiki/Model-Other-Than-User).
78
+ # Make the billable table the same name as your ActiveRecord table name for the model
79
+ # This is optional.
80
+ # Once you update the billable class, the table name will use the ActiveRecord inflected table name
81
+ config.billable_table = "teams"
82
+ end
83
+ ```
73
84
 
74
85
  #### Run the Migrations
75
86
 
76
87
  Finally, run the migrations with `$ rake db:migrate`
77
88
 
78
- #### Getting NoMethodError?
89
+ #### Getting NoMethodError?
79
90
 
80
91
  `NoMethodError (undefined method 'stripe_customer' for #<User:0x00007fbc34b9bf20>)`
81
92
 
82
93
  Fully restart your Rails application `bin/spring stop && rails s`
83
94
 
95
+ ## Payment Providers
96
+
97
+ We support both Stripe and Braintree and make our best attempt to
98
+ standardize the two. They function differently so keep that in mind if
99
+ you plan on doing more complex payments. It would be best to stick with
100
+ a single payment provider in that case so you don't run into
101
+ discrepancies.
102
+
84
103
  #### Stripe
85
104
 
86
105
  You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
@@ -97,6 +116,30 @@ You can also use the `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environmen
97
116
 
98
117
  **To see how to use Stripe Elements JS & Devise, [click here](https://github.com/jasoncharnes/pay/wiki/Using-Stripe-Elements-and-Devise).**
99
118
 
119
+ ##### Strong Customer Authentication (SCA)
120
+
121
+ 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.
122
+
123
+ Subscriptions that require SCA are marked as `incomplete` by default.
124
+ Once payment is authenticated, Stripe will send a webhook updating the
125
+ status of the subscription. You'll need to use the [Stripe CLI](https://github.com/stripe/stripe-cli) to forward
126
+ webhooks to your application to make sure your subscriptions work
127
+ correctly for SCA payments.
128
+
129
+ ```bash
130
+ stripe listen --forward-to localhost:3000/pay/webhooks/stripe
131
+ ```
132
+
133
+ 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).
134
+
135
+ **Payment Confirmations**
136
+
137
+ Sometimes you'll have a payment that requires extra authentication. In this case, Pay provides a webhook and action for handling these payments. It will automatically email the customer and provide a link with the PaymentIntent ID in the url where the customer will be asked to fill out their name and card number to confirm the payment. Once done, they'll be redirected back to your application.
138
+
139
+ If you'd like to change the views of the payment confirmation page, you can install the views using the generator and modify the template.
140
+
141
+ [<img src="https://d1jfzjx68gj8xs.cloudfront.net/items/2s3Z0J3Z3b1J1v2K2O1a/Screen%20Shot%202019-10-10%20at%2012.56.32%20PM.png?X-CloudApp-Visitor-Id=51470" alt="Stripe SCA Payment Confirmation" style="zoom: 25%;" />](https://d1jfzjx68gj8xs.cloudfront.net/items/2s3Z0J3Z3b1J1v2K2O1a/Screen%20Shot%202019-10-10%20at%2012.56.32%20PM.png)
142
+
100
143
  #### Background jobs
101
144
 
102
145
  If a user's email is updated and they have a `processor_id` set, Pay will enqueue a background job (EmailSyncJob) to sync the email with the payment processor.
@@ -137,7 +180,7 @@ Pay.setup do |config|
137
180
  config.send_emails = true
138
181
 
139
182
  config.automount_webhook_routes = true
140
- config.webhooks_path = "/webhooks" # Only when automount_webhook_routes is true
183
+ config.routes_path = "/pay" # Only when automount_webhook_routes is true
141
184
  end
142
185
  ```
143
186
 
@@ -155,19 +198,38 @@ Pay.setup do |config|
155
198
  end
156
199
  ```
157
200
 
201
+ ### Credentials
202
+
203
+ You'll need to add your private Stripe API key to your Rails secrets `config/secrets.yml`, credentials `rails credentials:edit`
204
+
205
+ ```yaml
206
+ development:
207
+ stripe:
208
+ private_key: xxxx
209
+ public_key: yyyy
210
+ signing_secret: zzzz
211
+ braintree:
212
+ private_key: xxxx
213
+ public_key: yyyy
214
+ ```
215
+
216
+ You can also use the `STRIPE_PUBLIC_KEY`, `STRIPE_PRIVATE_KEY` and `STRIPE_SIGNING_SECRET` environment variables.
217
+
158
218
  ### Generators
159
219
 
160
- #### Email Templates
220
+ If you want to modify the Stripe SCA template or any other views, you can copy over the view files using:
221
+
222
+ ```bash
223
+ bin/rails generate pay:views
224
+ ```
161
225
 
162
226
  If you want to modify the email templates, you can copy over the view files using:
163
227
 
228
+ ```bash
229
+ bin/rails generate pay:email_views
164
230
  ```
165
- $ bin/rails generate pay:email_views
166
- ```
167
-
168
- ## Emails
169
231
 
170
- ### Stripe
232
+ ### Emails
171
233
 
172
234
  Emails can be enabled/disabled using the `send_emails` configuration option (enabled per default). When enabled, the following emails will be sent:
173
235
 
@@ -175,7 +237,7 @@ Emails can be enabled/disabled using the `send_emails` configuration option (ena
175
237
  - When a charge was refunded
176
238
  - When a subscription is about to renew
177
239
 
178
- ## User API
240
+ ## Billable API
179
241
 
180
242
  #### Trials
181
243
 
@@ -214,7 +276,7 @@ user.on_generic_trial? #=> true
214
276
  user = User.find_by(email: 'michael@bluthcompany.co')
215
277
 
216
278
  user.processor = 'stripe'
217
- user.card_token = 'stripe-token'
279
+ user.card_token = 'payment_method_id'
218
280
  user.charge(1500) # $15.00 USD
219
281
 
220
282
  user = User.find_by(email: 'michael@bluthcompany.co')
@@ -236,7 +298,7 @@ different currencies, etc.
236
298
  user = User.find_by(email: 'michael@bluthcompany.co')
237
299
 
238
300
  user.processor = 'stripe'
239
- user.card_token = 'stripe-token'
301
+ user.card_token = 'payment_method_id'
240
302
  user.subscribe
241
303
  ```
242
304
 
@@ -260,7 +322,9 @@ Plan is the plan ID from the payment processor.
260
322
 
261
323
  ##### Options
262
324
 
263
- They do something?
325
+ By default, the trial specified on the subscription will be used.
326
+
327
+ `trial_period_days: 30` can be set to override and a trial to the subscription. This works the same for Braintree and Stripe.
264
328
 
265
329
  #### Retrieving a Subscription from the Database
266
330
 
@@ -329,7 +393,7 @@ user.customer #> Stripe or Braintree customer account
329
393
  ```ruby
330
394
  user = User.find_by(email: 'tobias@bluthcompany.co')
331
395
 
332
- user.update_card('stripe-token')
396
+ user.update_card('payment_method_id')
333
397
  ```
334
398
 
335
399
  #### Retrieving a Customer's Subscription from the Processor
@@ -445,9 +509,13 @@ Rails.application.config.to_prepare do
445
509
  end
446
510
  ```
447
511
 
448
- ## Webhooks
512
+ ## Routes & Webhooks
513
+
514
+ Routes are automatically mounted to `/pay` by default.
515
+
516
+ We provide a route for confirming SCA payments at `/pay/payments/:payment_intent_id`
449
517
 
450
- Webhooks are automatically mounted to `/webhooks/{provider}`.
518
+ Webhooks are automatically mounted at `/pay/webhooks/{provider}`
451
519
 
452
520
  #### Customizing webhook mount path
453
521
 
@@ -455,7 +523,7 @@ If you have a catch all route (for 404s etc) and need to control where/when the
455
523
 
456
524
  ```ruby
457
525
  # config/initializers/pay.rb
458
- config.automount_webhook_routes = false
526
+ config.automount_routes = false
459
527
 
460
528
  # config/routes.rb
461
529
  mount Pay::Engine, at: '/secret-webhook-path'
@@ -466,7 +534,7 @@ If you just want to modify where the engine mounts it's routes then you can chan
466
534
  ```ruby
467
535
  # config/initializers/pay.rb
468
536
 
469
- config.webhooks_path = '/secret-webhook-path'
537
+ config.routes_path = '/secret-webhook-path'
470
538
  ```
471
539
 
472
540
  ## Contributors
data/Rakefile CHANGED
@@ -1,40 +1,40 @@
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 "rdoc/task"
8
8
 
9
9
  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')
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")
15
15
  end
16
16
 
17
- APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
18
 
19
- load 'rails/tasks/engine.rake'
20
- load 'rails/tasks/statistics.rake'
19
+ load "rails/tasks/engine.rake"
20
+ load "rails/tasks/statistics.rake"
21
21
 
22
- require 'bundler/gem_tasks'
23
- require 'rake/testtask'
22
+ require "bundler/gem_tasks"
23
+ require "rake/testtask"
24
24
 
25
25
  Rake::TestTask.new(:test) do |t|
26
- t.libs << 'lib'
27
- t.libs << 'test'
28
- t.pattern = 'test/**/*_test.rb'
26
+ t.libs << "lib"
27
+ t.libs << "test"
28
+ t.pattern = "test/**/*_test.rb"
29
29
  t.verbose = false
30
30
  end
31
31
 
32
32
  task default: :test
33
33
 
34
34
  task :console do
35
- require 'irb'
36
- require 'irb/completion'
37
- require 'pay'
35
+ require "irb"
36
+ require "irb/completion"
37
+ require "pay"
38
38
  ARGV.clear
39
39
  IRB.start
40
40
  end
@@ -0,0 +1,7 @@
1
+ module Pay
2
+ class PaymentsController < ApplicationController
3
+ def show
4
+ @payment = Payment.from_id(params[:id])
5
+ end
6
+ end
7
+ end
@@ -7,13 +7,15 @@ module Pay
7
7
 
8
8
  def create
9
9
  case webhook_notification.kind
10
- when 'subscription_charged_successfully'
10
+ when "subscription_charged_successfully"
11
11
  subscription_charged_successfully(webhook_notification)
12
- when 'subscription_canceled'
12
+ when "subscription_canceled"
13
13
  subscription_canceled(webhook_notification)
14
+ when "subscription_trial_ended"
15
+ subscription_trial_ended(webhook_notification)
14
16
  end
15
17
 
16
- render json: { success: true }, status: :ok
18
+ render json: {success: true}, status: :ok
17
19
  rescue ::Braintree::InvalidSignature => e
18
20
  head :ok
19
21
  end
@@ -24,9 +26,10 @@ module Pay
24
26
  subscription = event.subscription
25
27
  return if subscription.nil?
26
28
 
27
- user = Pay.user_model.find_by(processor: :braintree, processor_id: subscription.id)
28
- return unless user.present?
29
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
30
+ return unless pay_subscription.present?
29
31
 
32
+ user = pay_subscription.owner
30
33
  charge = user.save_braintree_transaction(subscription.transactions.first)
31
34
 
32
35
  if Pay.send_emails
@@ -45,8 +48,18 @@ module Pay
45
48
  user.update(braintree_subscription_id: nil)
46
49
  end
47
50
 
51
+ def subscription_trial_ended(event)
52
+ subscription = event.subscription
53
+ return if subscription.nil?
54
+
55
+ pay_subscription = Pay.subscription_model.find_by(processor: :braintree, processor_id: subscription.id)
56
+ return unless pay_subscription.present?
57
+
58
+ pay_subscription.update(trial_ends_at: Time.zone.now)
59
+ end
60
+
48
61
  def webhook_notification
49
- @webhook_notification ||= ::Braintree::WebhookNotification.parse(
62
+ @webhook_notification ||= Pay.braintree_gateway.webhook_notification.parse(
50
63
  params[:bt_signature],
51
64
  params[:bt_payload]
52
65
  )
@@ -1,6 +1,6 @@
1
1
  module Pay
2
2
  class ApplicationMailer < ActionMailer::Base
3
- default from: 'from@example.com'
4
- layout 'mailer'
3
+ default from: Pay.support_email
4
+ layout "mailer"
5
5
  end
6
6
  end
@@ -23,12 +23,24 @@ module Pay
23
23
  end
24
24
 
25
25
  def subscription_renewing(user, subscription)
26
+ @user, @subscription = user, subscription
27
+
26
28
  mail(
27
29
  to: to(user),
28
30
  subject: Pay.email_renewing_subject,
29
31
  )
30
32
  end
31
33
 
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
+ )
42
+ end
43
+
32
44
  private
33
45
 
34
46
  def to(user)
@@ -2,6 +2,8 @@ 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]
6
+
5
7
  # Associations
6
8
  belongs_to :owner, class_name: Pay.billable_class, foreign_key: :owner_id
7
9
 
@@ -11,13 +13,16 @@ module Pay
11
13
  validates :processor_id, presence: true
12
14
  validates :processor_plan, presence: true
13
15
  validates :quantity, presence: true
16
+ validates :status, presence: true
14
17
 
15
18
  # Scopes
16
19
  scope :for_name, ->(name) { where(name: name) }
17
- scope :on_trial, ->{ where.not(trial_ends_at: nil).where("? < trial_ends_at", Time.zone.now) }
18
- scope :cancelled, ->{ where.not(ends_at: nil) }
19
- scope :on_grace_period, ->{ cancelled.where("? < ends_at", Time.zone.now) }
20
- scope :active, ->{ where(ends_at: nil).or(on_grace_period).or(on_trial) }
20
+ scope :on_trial, -> { where.not(trial_ends_at: nil).where("trial_ends_at > ?", Time.zone.now) }
21
+ scope :cancelled, -> { where.not(ends_at: nil) }
22
+ scope :on_grace_period, -> { cancelled.where("ends_at > ?", Time.zone.now) }
23
+ scope :active, -> { where(ends_at: nil).or(on_grace_period).or(on_trial) }
24
+ scope :incomplete, -> { where(status: :incomplete) }
25
+ scope :past_due, -> { where(status: :past_due) }
21
26
 
22
27
  attribute :prorate, :boolean, default: true
23
28
 
@@ -33,16 +38,32 @@ module Pay
33
38
  trial_ends_at? && Time.zone.now < trial_ends_at
34
39
  end
35
40
 
36
- def cancelled?
41
+ def canceled?
37
42
  ends_at?
38
43
  end
39
44
 
45
+ def cancelled?
46
+ canceled?
47
+ end
48
+
40
49
  def on_grace_period?
41
- cancelled? && Time.zone.now < ends_at
50
+ canceled? && Time.zone.now < ends_at
42
51
  end
43
52
 
44
53
  def active?
45
- ends_at.nil? || on_grace_period? || on_trial?
54
+ ["trialing", "active"].include?(status) && (ends_at.nil? || on_grace_period? || on_trial?)
55
+ end
56
+
57
+ def past_due?
58
+ status == "past_due"
59
+ end
60
+
61
+ def incomplete?
62
+ status == "incomplete"
63
+ end
64
+
65
+ def has_incomplete_payment?
66
+ past_due? || incomplete?
46
67
  end
47
68
 
48
69
  def cancel
@@ -56,7 +77,7 @@ module Pay
56
77
  def resume
57
78
  unless on_grace_period?
58
79
  raise StandardError,
59
- 'You can only resume subscriptions within their grace period.'
80
+ "You can only resume subscriptions within their grace period."
60
81
  end
61
82
 
62
83
  send("#{processor}_resume")
@@ -70,8 +91,18 @@ module Pay
70
91
  update(processor_plan: plan, ends_at: nil)
71
92
  end
72
93
 
73
- def processor_subscription
74
- owner.processor_subscription(processor_id)
94
+ def swap_and_invoice(plan)
95
+ swap(plan)
96
+ owner.invoice!(subscription_id: processor_id)
97
+ end
98
+
99
+ def processor_subscription(options = {})
100
+ owner.processor_subscription(processor_id, options)
101
+ end
102
+
103
+ def latest_payment
104
+ return unless stripe?
105
+ processor_subscription(expand: ["latest_invoice.payment_intent"]).latest_invoice.payment_intent
75
106
  end
76
107
  end
77
108
  end
@@ -1,12 +1,18 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
- <head>
4
- <title>Pay</title>
5
- <%= stylesheet_link_tag "pay/application", media: "all" %>
6
- <%= javascript_include_tag "pay/application" %>
3
+ <head class="h-full">
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Payment Confirmation - <%= Pay.business_name %></title>
7
+ <%#= stylesheet_link_tag "pay/application", media: "all" %>
8
+ <%#= javascript_include_tag "pay/application" %>
7
9
  <%= csrf_meta_tags %>
10
+
11
+ <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
12
+ <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
13
+ <script src="https://js.stripe.com/v3"></script>
8
14
  </head>
9
- <body>
15
+ <body class="font-sans text-gray-600 bg-gray-200 leading-normal p-4 h-full">
10
16
 
11
17
  <%= yield %>
12
18