diff --git a/app/controllers/accounts/omniauth_callbacks_controller.rb b/app/controllers/accounts/omniauth_callbacks_controller.rb index ca7c9e2..18401fd 100644 --- a/app/controllers/accounts/omniauth_callbacks_controller.rb +++ b/app/controllers/accounts/omniauth_callbacks_controller.rb @@ -17,7 +17,11 @@ def google_oauth2 end def after_sign_in_path_for(resource_or_scope) - stored_location_for(resource_or_scope) || root_path + stored = stored_location_for(resource_or_scope) + return stored if stored.present? + return page_checkout_path(resource_or_scope) if resource_or_scope.is_a?(Account) && resource_or_scope.requires_payment? + + root_path end private diff --git a/app/controllers/accounts/registrations_controller.rb b/app/controllers/accounts/registrations_controller.rb index 254181a..c9ccf51 100644 --- a/app/controllers/accounts/registrations_controller.rb +++ b/app/controllers/accounts/registrations_controller.rb @@ -35,8 +35,11 @@ def create end def after_sign_up_path_for(account) - # page_setup_index_path(account) - page_path(account) + if Rails.configuration.multiuser_mode + page_checkout_path(account) + else + page_path(account) + end end def update_resource(resource, params) diff --git a/app/controllers/accounts/sessions_controller.rb b/app/controllers/accounts/sessions_controller.rb index cf6b963..1bb18e6 100644 --- a/app/controllers/accounts/sessions_controller.rb +++ b/app/controllers/accounts/sessions_controller.rb @@ -27,7 +27,11 @@ class SessionsController < Devise::SessionsController # end def after_sign_in_path_for(account) - stored_location_for(account) || root_path + stored = stored_location_for(account) + return stored if stored.present? + return page_checkout_path(account) if account.is_a?(Account) && account.requires_payment? + + root_path end end end diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 107cbe5..064f907 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -6,7 +6,7 @@ class CheckoutController < ApplicationController before_action :redirect_in_solo def show - url = @account.checkout_url(page_setup_url(@account, :domain), page_url(@account)) + url = @account.checkout_url(page_url(@account), page_url(@account)) redirect_to url, status: :found, allow_other_host: true end diff --git a/app/controllers/concerns/payment_required.rb b/app/controllers/concerns/payment_required.rb new file mode 100644 index 0000000..069ecf6 --- /dev/null +++ b/app/controllers/concerns/payment_required.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module PaymentRequired + extend ActiveSupport::Concern + + included do + before_action :require_payment + end + + private + + def require_payment + return unless current_account&.requires_payment? + return if params.key?(:session_id) # Just returned from Stripe, webhook pending + + redirect_to page_checkout_path(current_account) + end +end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 6d86eb2..bfdffcf 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -2,6 +2,7 @@ class PagesController < ApplicationController prepend_before_action :authenticate_account! + include PaymentRequired before_action :set_account layout 'dashboard' diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index ecdfe07..e0c78c3 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -2,6 +2,7 @@ class PostsController < ApplicationController prepend_before_action :authenticate_account! + include PaymentRequired before_action :set_account_from_path before_action :set_post, only: %i[destroy edit update] diff --git a/app/controllers/showcase_controller.rb b/app/controllers/showcase_controller.rb index e2b60a0..dd484bf 100644 --- a/app/controllers/showcase_controller.rb +++ b/app/controllers/showcase_controller.rb @@ -2,6 +2,7 @@ class ShowcaseController < ApplicationController prepend_before_action :authenticate_account! + include PaymentRequired before_action :set_account_from_path layout 'dashboard_container' diff --git a/app/controllers/subscribers_controller.rb b/app/controllers/subscribers_controller.rb index ddb2656..97c0578 100644 --- a/app/controllers/subscribers_controller.rb +++ b/app/controllers/subscribers_controller.rb @@ -4,6 +4,7 @@ class SubscribersController < ApplicationController prepend_before_action :authenticate_account! + include PaymentRequired before_action :set_account_from_path layout 'dashboard_container' diff --git a/app/models/account.rb b/app/models/account.rb index b41fe3e..a455f88 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -131,6 +131,20 @@ def active_subscription? payment_processor&.subscribed? end + def requires_payment? + return false if Rails.configuration.solo_mode + return false if grandfathered? + return false if ever_subscribed? + + !payment_processor&.subscribed? + end + + def ever_subscribed? + Pay::Subscription.joins(:customer) + .where(pay_customers: { owner_type: 'Account', owner_id: id }) + .exists? + end + def unverified_domain? domains.each do |domain| return true unless domain.verified? @@ -212,6 +226,7 @@ def checkout_url(success_url, cancel_url) # rubocop:disable Metrics/MethodLength allow_promotion_codes: true, billing_address_collection: 'auto', payment_method_collection: 'if_required', + subscription_data: { trial_period_days: 30 }, customer_update: { address: 'auto', name: 'auto' diff --git a/app/views/layouts/dashboard.html.erb b/app/views/layouts/dashboard.html.erb index b12b76d..120acf6 100644 --- a/app/views/layouts/dashboard.html.erb +++ b/app/views/layouts/dashboard.html.erb @@ -42,7 +42,7 @@ secondaryMenuItems = [ name: "Billing", newTab: false, to: page_billing_path(@account), - show: @account&.active_subscription? && Rails.configuration.multiuser_mode + show: @account&.payment_processor&.subscribed? && Rails.configuration.multiuser_mode }, { name: "Queue", diff --git a/app/views/marketing_pages/_cta.html.erb b/app/views/marketing_pages/_cta.html.erb index 758d9d6..ee42713 100644 --- a/app/views/marketing_pages/_cta.html.erb +++ b/app/views/marketing_pages/_cta.html.erb @@ -7,7 +7,7 @@ Ready to set up your website?

Have your page published in minutes.

- Get started → + Start free trial →

- (Check out a recent example here) + (Check out a recent example here)

diff --git a/app/views/welcome_mailer/social_networks_over.html.erb b/app/views/welcome_mailer/social_networks_over.html.erb index e3235c2..ddb45ae 100644 --- a/app/views/welcome_mailer/social_networks_over.html.erb +++ b/app/views/welcome_mailer/social_networks_over.html.erb @@ -27,7 +27,7 @@ If you are ready to break the cycle on addictive social networks, try setting up a personal newsletter with Postcard. You can still participate in social networks by sharing posts on those sites. But you can build a newsletter over time that you own. 

-

Originally published on philipithomas.com.

+

Originally published on philipithomas.postcard.page.

<% content_for :button_label do %>Write your newsletter<% end %> diff --git a/config/brakeman.ignore b/config/brakeman.ignore index c8784ff..af154cd 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -60,20 +60,20 @@ { "warning_type": "Redirect", "warning_code": 18, - "fingerprint": "7091306dd0b02a1c02eaadd5138fef97feb5c99ac385b19973467ed117fc84e9", + "fingerprint": "e0630695d24b9edc6f4723712fc38efa47e131e46fd11380309fa39f79e3e6c5", "check_name": "Redirect", "message": "Possible unprotected redirect", "file": "app/controllers/checkout_controller.rb", "line": 10, "link": "https://brakemanscanner.org/docs/warning_types/redirect/", - "code": "redirect_to(Account.friendly.find(params[:page_slug]).checkout_url(page_setup_url(Account.friendly.find(params[:page_slug]), :domain), page_url(Account.friendly.find(params[:page_slug]))), :status => :found, :allow_other_host => true)", + "code": "redirect_to(Account.friendly.find(params[:page_slug]).checkout_url(page_url(Account.friendly.find(params[:page_slug])), page_url(Account.friendly.find(params[:page_slug]))), :status => :found, :allow_other_host => true)", "render_path": null, "location": { "type": "method", "class": "CheckoutController", "method": "show" }, - "user_input": "Account.friendly.find(params[:page_slug]).checkout_url(page_setup_url(Account.friendly.find(params[:page_slug]), :domain), page_url(Account.friendly.find(params[:page_slug])))", + "user_input": "Account.friendly.find(params[:page_slug]).checkout_url(page_url(Account.friendly.find(params[:page_slug])), page_url(Account.friendly.find(params[:page_slug])))", "confidence": "Weak", "cwe_id": [ 601 diff --git a/db/migrate/20260312034422_add_grandfathered_to_accounts.rb b/db/migrate/20260312034422_add_grandfathered_to_accounts.rb new file mode 100644 index 0000000..9d5925c --- /dev/null +++ b/db/migrate/20260312034422_add_grandfathered_to_accounts.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddGrandfatheredToAccounts < ActiveRecord::Migration[7.1] + def up + add_column :accounts, :grandfathered, :boolean, default: false, null: false + Account.update_all(grandfathered: true) + end + + def down + remove_column :accounts, :grandfathered + end +end diff --git a/db/schema.rb b/db/schema.rb index 62398b7..a289c09 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_06_10_215212) do +ActiveRecord::Schema[7.1].define(version: 2026_03_12_034422) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -42,6 +42,7 @@ t.bigint "ahoy_signup_visit_id" t.bigint "pinned_post_id" t.datetime "locked_at" + t.boolean "grandfathered", default: false, null: false t.index ["confirmation_token"], name: "index_accounts_on_confirmation_token", unique: true t.index ["email"], name: "index_accounts_on_email", unique: true t.index ["pinned_post_id"], name: "index_accounts_on_pinned_post_id" diff --git a/test/fixtures/accounts.yml b/test/fixtures/accounts.yml new file mode 100644 index 0000000..42598e1 --- /dev/null +++ b/test/fixtures/accounts.yml @@ -0,0 +1,15 @@ +grandfathered_user: + name: Grandfathered User + email: grandfathered@example.com + encrypted_password: <%= Devise::Encryptor.digest(Account, 'password123') %> + slug: grandfathered-user + grandfathered: true + accent_color: "#2c6153" + +new_user: + name: New User + email: newuser@example.com + encrypted_password: <%= Devise::Encryptor.digest(Account, 'password123') %> + slug: new-user + grandfathered: false + accent_color: "#2c6153" diff --git a/test/models/account_test.rb b/test/models/account_test.rb new file mode 100644 index 0000000..6c2ee91 --- /dev/null +++ b/test/models/account_test.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'test_helper' + +class AccountTest < ActiveSupport::TestCase + setup do + @original_solo_mode = Rails.configuration.solo_mode + @original_multiuser_mode = Rails.configuration.multiuser_mode + end + + teardown do + Rails.configuration.solo_mode = @original_solo_mode + Rails.configuration.multiuser_mode = @original_multiuser_mode + end + + test "active_subscription? returns true in solo mode" do + Rails.configuration.solo_mode = true + account = accounts(:new_user) + assert account.active_subscription? + end + + test "active_subscription? returns false for non-subscribed multiuser account" do + Rails.configuration.solo_mode = false + account = accounts(:new_user) + refute account.active_subscription? + end + + test "requires_payment? returns false in solo mode" do + Rails.configuration.solo_mode = true + account = accounts(:new_user) + refute account.requires_payment? + end + + test "requires_payment? returns false for grandfathered accounts" do + Rails.configuration.solo_mode = false + account = accounts(:grandfathered_user) + refute account.requires_payment? + end + + test "requires_payment? returns true for new non-grandfathered non-subscribed accounts" do + Rails.configuration.solo_mode = false + account = accounts(:new_user) + assert account.requires_payment? + end + + test "ever_subscribed? returns false for fresh accounts" do + account = accounts(:new_user) + refute account.ever_subscribed? + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..96b6e32 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +require 'rails/test_help' + +class ActiveSupport::TestCase + fixtures :all +end