Remove payment/subscription system and card/storage limits

Fizzy is now free. Remove the entire Stripe billing system,
subscription management, and card/storage limit enforcement.

Removes from saas/: Plan model, Account::Billing, Account::Subscription,
Account::Limited, Account::OverriddenLimits, Account::BillingWaiver,
all subscription/billing controllers and views, Stripe webhook handler,
card creation/publishing limit enforcement, admin account override UI,
usage report rake task, and all related tests.

Removes from main app: Fizzy.saas? guards for subscription panel,
SaaS card footer override, near-limit notices, and saas.css stylesheet.

Adds migration to drop billing tables from the SaaS database.

Non-billing SaaS features (push notifications, signup, authorization,
telemetry, console1984/audits1984) are preserved.
This commit is contained in:
Jorge Manrubia
2026-03-17 09:22:04 +01:00
parent bead275d27
commit 964915bc66
74 changed files with 9 additions and 2221 deletions
@@ -1,70 +0,0 @@
/* Subscriptions
/* ------------------------------------------------------------------------ */
:root {
--settings-subscription-background: linear-gradient(to bottom, var(--color-canvas), oklch(var(--lch-violet-lighter)));
--settings-subscription-color: oklch(var(--lch-violet-medium));
--settings-subscription-text-color: oklch(var(--lch-violet-dark));
.settings-subscription__button {
--btn-background: var(--settings-subscription-color);
--btn-border-color: var(--color-canvas);
--btn-color: var(--color-canvas);
--focus-ring-color: var(--color-ink);
}
.settings-subscription__divider {
--divider-color: currentColor;
color: var(--settings-subscription-color);
margin-block-start: calc(var(--block-space-half) * -1);
}
.settings-subscription__footer {
color: var(--settings-subscription-text-color);
&::before {
border-block-start: 1px solid var(--settings-subscription-text-color);
content: "";
display: block;
inline-size: 8ch;
margin: 0 auto 1ch;
}
}
.settings-subscription__link {
color: var(--settings-subscription-text-color);
}
.settings-subscription__notch {
animation: wiggle 500ms ease;
background: var(--settings-subscription-background);
border-radius: 3em;
box-shadow: 0 0 0.3em 0.2em var(--settings-subscription-color);
color: var(--settings-subscription-text-color);
margin-inline: auto;
padding: 0.3em 0.3em 0.3em 1.2em;
transform: rotate(-1deg);
@media (max-width: 640px) {
border-radius: 1ch;
padding-block: 1ch;
padding-inline: 2ch;
}
.btn {
/* margin-block: 0.3em; */
}
}
.settings-subscription__panel {
background: var(--settings-subscription-background);
}
.settings_subscription__warning {
color: var(--settings-subscription-text-color);
a {
color: var(--settings-subscription-text-color);
}
}
}
@@ -1,19 +0,0 @@
class Account::BillingPortalsController < ApplicationController
before_action :ensure_admin
before_action :ensure_subscribed_account
def show
redirect_to create_stripe_billing_portal_session.url, allow_other_host: true
end
private
def ensure_subscribed_account
unless Current.account.subscribed?
redirect_to account_subscription_path, alert: "No billing information found"
end
end
def create_stripe_billing_portal_session
Stripe::BillingPortal::Session.create(customer: Current.account.subscription.stripe_customer_id, return_url: account_settings_url)
end
end
@@ -1,12 +0,0 @@
class Account::Subscriptions::DowngradesController < Account::Subscriptions::UpdatePlanController
before_action :ensure_downgradeable
private
def target_plan
Plan.paid
end
def ensure_downgradeable
head :bad_request unless subscription.plan == Plan.paid_with_extra_storage
end
end
@@ -1,32 +0,0 @@
class Account::Subscriptions::UpdatePlanController < ApplicationController
before_action :ensure_admin
def create
portal_session = Stripe::BillingPortal::Session.create(
customer: subscription.stripe_customer_id,
return_url: account_settings_url(anchor: "subscription"),
flow_data: {
type: "subscription_update_confirm",
subscription_update_confirm: {
subscription: subscription.stripe_subscription_id,
items: [ { id: stripe_subscription_item_id, price: target_plan.stripe_price_id } ]
}
}
)
redirect_to portal_session.url, allow_other_host: true
end
private
def target_plan
raise NotImplementedError
end
def subscription
@subscription ||= Current.account.subscription
end
def stripe_subscription_item_id
Stripe::Subscription.retrieve(subscription.stripe_subscription_id).items.data.first.id
end
end
@@ -1,12 +0,0 @@
class Account::Subscriptions::UpgradesController < Account::Subscriptions::UpdatePlanController
before_action :ensure_upgradeable
private
def target_plan
Plan.paid_with_extra_storage
end
def ensure_upgradeable
head :bad_request unless subscription.plan == Plan.paid
end
end
@@ -1,46 +0,0 @@
class Account::SubscriptionsController < ApplicationController
before_action :ensure_admin
before_action :set_stripe_session, only: :show
def show
end
def create
session = Stripe::Checkout::Session.create \
customer: find_or_create_stripe_customer,
mode: "subscription",
line_items: [ { price: plan_param.stripe_price_id, quantity: 1 } ],
success_url: account_subscription_url + "?session_id={CHECKOUT_SESSION_ID}",
cancel_url: account_subscription_url,
metadata: { account_id: Current.account.id, plan_key: plan_param.key },
automatic_tax: { enabled: true },
tax_id_collection: { enabled: true },
billing_address_collection: "required",
customer_update: { address: "auto", name: "auto" }
redirect_to session.url, allow_other_host: true
end
private
def plan_param
@plan_param ||= Plan[params[:plan_key]] || Plan.paid
end
def set_stripe_session
@stripe_session = Stripe::Checkout::Session.retrieve(params[:session_id]) if params[:session_id]
end
def find_or_create_stripe_customer
find_stripe_customer || create_stripe_customer
end
def find_stripe_customer
Stripe::Customer.retrieve(Current.account.subscription.stripe_customer_id) if Current.account.subscription&.stripe_customer_id
end
def create_stripe_customer
Stripe::Customer.create(email: Current.user.identity.email_address, name: Current.account.name, metadata: { account_id: Current.account.id }).tap do |customer|
Current.account.create_subscription!(stripe_customer_id: customer.id, plan_key: plan_param.key, status: "incomplete")
end
end
end
@@ -1,5 +0,0 @@
class Admin::AccountSearchesController < AdminController
def create
redirect_to saas.edit_admin_account_path(params[:q])
end
end
@@ -1,27 +0,0 @@
class Admin::AccountsController < AdminController
include Admin::AccountScoped
layout "public"
before_action :set_account, only: %i[ edit update ]
def index
end
def edit
end
def update
@account.override_limits(**overridden_limits_params.to_h.symbolize_keys)
redirect_to saas.edit_admin_account_path(@account.external_account_id), notice: "Account limits updated"
end
private
def set_account
@account = Account.find_by!(external_account_id: params[:id])
end
def overridden_limits_params
params.expect(account: [ :card_count, :bytes_used ])
end
end
@@ -1,13 +0,0 @@
class Admin::BillingWaiversController < AdminController
include Admin::AccountScoped
def create
@account.comp
redirect_to saas.edit_admin_account_path(@account.external_account_id), notice: "Account comped"
end
def destroy
@account.uncomp
redirect_to saas.edit_admin_account_path(@account.external_account_id), notice: "Account uncomped"
end
end
@@ -1,8 +0,0 @@
class Admin::OverriddenLimitsController < AdminController
include Admin::AccountScoped
def destroy
@account.reset_overridden_limits
redirect_to saas.edit_admin_account_path(@account.external_account_id), notice: "Limits reset"
end
end
@@ -6,10 +6,6 @@ class Admin::StatsController < AdminController
@accounts_last_7_days = Account.where(created_at: 7.days.ago..).count
@accounts_last_24_hours = Account.where(created_at: 24.hours.ago..).count
@paid_accounts_total = Account::Subscription.paid.distinct.count(:account_id)
@paid_accounts_last_7_days = Account::Subscription.paid.where(created_at: 7.days.ago..).distinct.count(:account_id)
@paid_accounts_last_24_hours = Account::Subscription.paid.where(created_at: 24.hours.ago..).distinct.count(:account_id)
@identities_total = Identity.count
@identities_last_7_days = Identity.where(created_at: 7.days.ago..).count
@identities_last_24_hours = Identity.where(created_at: 24.hours.ago..).count
@@ -1,12 +0,0 @@
module Admin::AccountScoped
extend ActiveSupport::Concern
included do
before_action :set_account
end
private
def set_account
@account = Account.find_by!(external_account_id: params[:account_id] || params[:id])
end
end
@@ -1,8 +0,0 @@
module Card::Limited
extend ActiveSupport::Concern
private
def ensure_under_limits
head :forbidden if Current.account.exceeding_limits?
end
end
@@ -1,11 +0,0 @@
module Card::LimitedCreation
extend ActiveSupport::Concern
included do
include Card::Limited
# Only limit API requests. We let you create drafts in the app to actually show the banner, no matter the card count.
# We limit card publications separately. See +Card::LimitedPublishing+.
before_action :ensure_under_limits, only: %i[ create ], if: -> { request.format.json? }
end
end
@@ -1,9 +0,0 @@
module Card::LimitedPublishing
extend ActiveSupport::Concern
included do
include Card::Limited
before_action :ensure_under_limits, only: %i[ create ]
end
end
@@ -1,85 +0,0 @@
class Stripe::WebhooksController < ApplicationController
allow_unauthenticated_access
skip_before_action :require_account
skip_forgery_protection
def create
if event = verify_webhook_signature
dispatch_stripe_event(event)
head :ok
else
head :bad_request
end
end
private
def dispatch_stripe_event(event)
case event.type
when "checkout.session.completed"
sync_new_subscription(event.data.object.subscription, plan_key: event.data.object.metadata["plan_key"]) if event.data.object.mode == "subscription"
when "customer.subscription.updated", "customer.subscription.deleted"
sync_subscription(event.data.object.id)
end
end
def verify_webhook_signature
payload = request.body.read
sig_header = request.env["HTTP_STRIPE_SIGNATURE"]
Stripe::Webhook.construct_event(payload, sig_header, ENV["STRIPE_WEBHOOK_SECRET"])
rescue Stripe::SignatureVerificationError => e
Rails.logger.error "Stripe webhook signature verification failed: #{e.message}"
nil
end
def sync_new_subscription(stripe_subscription_id, plan_key:)
sync_subscription(stripe_subscription_id) do |subscription_properties|
subscription_properties[:plan_key] = plan_key if plan_key
end
end
# Always fetch fresh subscription data from Stripe to handle out-of-order
# event delivery. Not relying on payload data.
def sync_subscription(stripe_subscription_id)
stripe_subscription = Stripe::Subscription.retrieve(stripe_subscription_id)
if subscription = find_subscription_by_stripe_customer(stripe_subscription.customer)
subscription_properties = {
stripe_subscription_id: stripe_subscription.id,
status: stripe_subscription.status,
current_period_end: current_period_end_for(stripe_subscription),
cancel_at: stripe_subscription.cancel_at ? Time.at(stripe_subscription.cancel_at) : nil,
next_amount_due_in_cents: next_amount_due_for(stripe_subscription),
plan_key: plan_key_for(stripe_subscription)
}
yield subscription_properties if block_given?
subscription_properties[:stripe_subscription_id] = nil if stripe_subscription.status == "canceled"
subscription.update!(subscription_properties)
end
end
def find_subscription_by_stripe_customer(id)
Account::Subscription.find_by(stripe_customer_id: id)
end
def current_period_end_for(stripe_subscription)
timestamp = stripe_subscription.items.data.first&.current_period_end
Time.at(timestamp) if timestamp
end
def next_amount_due_for(stripe_subscription)
return nil if stripe_subscription.status == "canceled"
preview = Stripe::Invoice.create_preview(customer: stripe_subscription.customer, subscription: stripe_subscription.id)
preview.amount_due
rescue Stripe::InvalidRequestError
nil
end
def plan_key_for(stripe_subscription)
price_id = stripe_subscription.items.data.first&.price&.id
Plan.find_by_price_id(price_id)&.key
end
end
-19
View File
@@ -1,19 +0,0 @@
module SubscriptionsHelper
def storage_to_human_size(bytes)
number_to_human_size(bytes).delete(" ")
end
def subscription_period_end_action(subscription)
if subscription.to_be_canceled?
"Your Fizzy subscription ends on"
elsif subscription.canceled?
"Your Fizzy subscription ended on"
else
"Your next payment is <b>#{ format_currency(subscription.next_amount_due) }</b> on".html_safe
end
end
def format_currency(amount)
number_to_currency(amount, precision: (amount % 1).zero? ? 0 : 2)
end
end
@@ -1,8 +0,0 @@
class Account::SyncStripeCustomerEmailJob < ApplicationJob
queue_as :default
retry_on Stripe::StripeError, wait: :polynomially_longer
def perform(subscription)
subscription.sync_customer_email_to_stripe
end
end
-50
View File
@@ -1,50 +0,0 @@
module Account::Billing
extend ActiveSupport::Concern
included do
has_one :subscription, class_name: "Account::Subscription", dependent: :destroy
has_one :billing_waiver, class_name: "Account::BillingWaiver", dependent: :destroy
set_callback :incinerate, :before, -> { subscription&.cancel }
set_callback :cancel, :after, -> { subscription&.pause }
set_callback :reactivate, :before, -> { subscription&.resume }
end
def plan
active_subscription&.plan || Plan.free
end
def subscribed?
subscription.present?
end
def comped?
billing_waiver.present?
end
def comp
create_billing_waiver unless billing_waiver
end
def uncomp
billing_waiver&.destroy!
reload_billing_waiver
end
def owner_email_changed
Account::SyncStripeCustomerEmailJob.perform_later(subscription) if subscription
end
private
def active_subscription
if comped?
comped_subscription
elsif subscription&.active?
subscription
end
end
def comped_subscription
@comped_subscription ||= billing_waiver&.subscription
end
end
@@ -1,7 +0,0 @@
class Account::BillingWaiver < SaasRecord
belongs_to :account
def subscription
@subscription ||= Account::Subscription.new(plan: Plan.paid_with_extra_storage)
end
end
-56
View File
@@ -1,56 +0,0 @@
module Account::Limited
extend ActiveSupport::Concern
included do
has_one :overridden_limits, class_name: "Account::OverriddenLimits", dependent: :destroy
end
NEAR_CARD_LIMIT_THRESHOLD = 100
NEAR_STORAGE_LIMIT_THRESHOLD = 500.megabytes
def override_limits(card_count: nil, bytes_used: nil)
(overridden_limits || build_overridden_limits).update!(card_count:, bytes_used:)
end
def billed_cards_count
overridden_limits&.card_count || cards_count
end
def billed_bytes_used
overridden_limits&.bytes_used || bytes_used
end
def nearing_plan_cards_limit?
plan.limit_cards? && remaining_cards_count <= NEAR_CARD_LIMIT_THRESHOLD
end
def exceeding_card_limit?
plan.limit_cards? && billed_cards_count >= plan.card_limit
end
def nearing_plan_storage_limit?
remaining_storage < NEAR_STORAGE_LIMIT_THRESHOLD
end
def exceeding_storage_limit?
billed_bytes_used > plan.storage_limit
end
def exceeding_limits?
exceeding_card_limit? || exceeding_storage_limit?
end
def reset_overridden_limits
overridden_limits&.destroy
reload_overridden_limits
end
private
def remaining_cards_count
plan.card_limit - billed_cards_count
end
def remaining_storage
plan.storage_limit - billed_bytes_used
end
end
@@ -1,4 +0,0 @@
# To ease testing of limits
class Account::OverriddenLimits < SaasRecord
belongs_to :account
end
-73
View File
@@ -1,73 +0,0 @@
class Account::Subscription < SaasRecord
belongs_to :account
enum :status, %w[ active past_due unpaid canceled incomplete incomplete_expired trialing paused ].index_by(&:itself)
scope :paid, -> {
where(status: %w[active trialing past_due])
.where(plan_key: Plan.all.select(&:paid?).map(&:key))
.where.not(account_id: Account::BillingWaiver.select(:account_id))
}
validates :plan_key, presence: true, inclusion: { in: Plan::PLANS.keys.map(&:to_s) }
delegate :paid?, to: :plan
def plan
@plan ||= Plan.find(plan_key)
end
def plan=(plan)
self.plan_key = plan.key
end
def to_be_canceled?
active? && cancel_at.present?
end
def next_amount_due
next_amount_due_in_cents ? next_amount_due_in_cents / 100.0 : plan.price
end
def pause
if stripe_subscription_id.present?
Stripe::Subscription.update(
stripe_subscription_id,
pause_collection: { behavior: "void" }
)
end
end
def resume
if stripe_subscription_id.present?
Stripe::Subscription.update(
stripe_subscription_id,
pause_collection: ""
)
end
end
def cancel
Stripe::Subscription.cancel(stripe_subscription_id) if stripe_subscription_id.present?
rescue Stripe::InvalidRequestError => e
# Subscription already deleted/canceled in Stripe - treat as success
Rails.logger.warn "Stripe subscription #{stripe_subscription_id} not found during cancel: #{e.message}"
end
def sync_customer_email_to_stripe
if stripe_customer_id && (email = owner_email)
Stripe::Customer.update(stripe_customer_id, email: email)
end
rescue Stripe::InvalidRequestError => e
# Customer already deleted in Stripe - treat as success
Rails.logger.warn "Stripe customer #{stripe_customer_id} not found during email sync: #{e.message}"
end
private
# Account owner email for Stripe customer record. Returns nil when:
# - No owner exists (ownership being transferred, account in limbo)
# - Owner has no identity (deactivated user)
def owner_email
account.users.owner.first&.identity&.email_address
end
end
-59
View File
@@ -1,59 +0,0 @@
class Plan
PLANS = {
free_v1: { name: "Free", price: 0, card_limit: 1000, storage_limit: 1.gigabytes },
monthly_v1: { name: "Unlimited", price: 20, card_limit: Float::INFINITY, storage_limit: 5.gigabytes, stripe_price_id: ENV["STRIPE_MONTHLY_V1_PRICE_ID"] },
monthly_extra_storage_v1: { name: "Unlimited + Extra Storage", price: 25, card_limit: Float::INFINITY, storage_limit: 500.gigabytes, stripe_price_id: ENV["STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID"] }
}
attr_reader :key, :name, :price, :card_limit, :storage_limit, :stripe_price_id
class << self
def all
@all ||= PLANS.map { |key, properties| new(key: key, **properties) }
end
def free
@free ||= find(:free_v1)
end
def paid
@paid ||= find(:monthly_v1)
end
def paid_with_extra_storage
@paid_with_extra_storage ||= find(:monthly_extra_storage_v1)
end
def find(key)
@all_by_key ||= all.index_by(&:key).with_indifferent_access
@all_by_key[key]
end
def find_by_price_id(price_id)
all.find { |plan| plan.stripe_price_id == price_id }
end
alias [] find
end
def initialize(key:, name:, price:, card_limit:, storage_limit:, stripe_price_id: nil)
@key = key
@name = name
@price = price
@card_limit = card_limit
@storage_limit = storage_limit
@stripe_price_id = stripe_price_id
end
def free?
price.zero?
end
def paid?
!free?
end
def limit_cards?
!card_limit.infinite?
end
end
@@ -1,23 +0,0 @@
module User::NotifiesAccountOfEmailChange
extend ActiveSupport::Concern
included do
after_update :notify_account_of_owner_change, if: :account_owner_changed?
end
private
# Account owner changed when:
# - The current owner changed their email
# - A user just became the owner (ownership transfer)
def account_owner_changed?
owner? && identity && (saved_change_to_identity_id? || became_owner?)
end
def became_owner?
saved_change_to_role? && role_before_last_save != "owner"
end
def notify_account_of_owner_change
account.owner_email_changed
end
end
@@ -1,18 +0,0 @@
<h3 class="margin-block-start txt-large font-weight-black txt-tight-lines">
<% if Current.account.exceeding_card_limit? && Current.account.exceeding_storage_limit? %>
Youve used up your <u><%= number_with_delimiter(Plan.free.card_limit) %></u> free cards and all <u><%= storage_to_human_size(Plan.free.storage_limit) %></u> of free storage
<% elsif Current.account.exceeding_card_limit? %>
Youve used up your <u><%= number_with_delimiter(Plan.free.card_limit) %></u> free cards
<% elsif Current.account.exceeding_storage_limit? %>
Youve used up all <u><%= storage_to_human_size(Plan.free.storage_limit) %></u> of free storage
<% else %>
Youve used <u><%= Current.account.billed_cards_count %></u> free cards out of <%= number_with_delimiter(Plan.free.card_limit) %>
<% end %>
</h3>
<p class="margin-block-start-half">To keep using Fizzy <%= "past #{ number_with_delimiter(Plan.free.card_limit) } cards," unless Current.account.exceeding_storage_limit? %> its only <strong><%= format_currency(Plan.paid.price) %>/month</strong> for <strong class="txt-nowrap">unlimited cards</strong>, <strong class="txt-nowrap">unlimited users</strong>, and <strong><%= storage_to_human_size(Plan.paid.storage_limit) %></strong> of storage.</p>
<%= button_to "Upgrade to #{Plan.paid.name} for #{ format_currency(Plan.paid.price) }/month", account_subscription_path, class: "btn settings-subscription__button txt-medium", form: { data: { turbo: false } } %>
<p>Cancel anytime, no contracts, take your data with you whenever.</p>
<p class="settings-subscription__footer txt-small margin-none-block-end">Right now youre on the <strong><%= Current.account.plan.name %></strong> plan which includes <%= number_with_delimiter(Plan.free.card_limit) %> cards, unlimited users, and <%= storage_to_human_size(Plan.free.storage_limit) %> of storage.</p>
@@ -1,21 +0,0 @@
<h3 class="margin-block-start txt-x-large font-weight-black txt-tight-lines">
<% if Current.account.exceeding_storage_limit? %>
Youve run out of storage
<% elsif Current.account.nearing_plan_storage_limit? %>
Youve used <strong><%= storage_to_human_size(Current.account.billed_bytes_used) %></strong> of storage
<% else %>
Thank you for buying Fizzy
<% end %>
</h3>
<% if Current.account.nearing_plan_storage_limit? || Current.account.exceeding_storage_limit? %>
<p class="margin-block-start-half">
The <strong><%= Current.account.plan.name %></strong> plan includes <strong><%= storage_to_human_size(Plan.paid.storage_limit) %></strong>. Upgrade to get <strong><%= storage_to_human_size(Plan.paid_with_extra_storage.storage_limit) %></strong> extra storage for <%= format_currency(Plan.paid_with_extra_storage.price - Plan.paid.price) %>/month more.
</p>
<%= button_to "Upgrade to #{Plan.paid_with_extra_storage.name} for #{ number_to_currency(Plan.paid_with_extra_storage.price, strip_insignificant_zeros: true) }/month", account_subscription_upgrade_path, class: "btn settings-subscription__button txt-medium", form: { data: { turbo: false } } %>
<% end %>
<% if Current.account.subscription %>
<%= render "account/settings/subscription", subscription: Current.account.subscription %>
<% end %>
@@ -1,20 +0,0 @@
<% if subscription.current_period_end %>
<p class="margin-block-start-half"><%= subscription_period_end_action(subscription) %> <strong><%= subscription.current_period_end.to_date.to_fs(:long) %></strong></p>
<% end %>
<p>
<%= link_to "Manage your subscription", account_billing_portal_path, class: "btn btn--plain settings-subscription__link txt-link", data: { turbo_prefetch: false } %>
</p>
<div class="settings-subscription__footer txt-small margin-block">
Right now youre on the <strong><%= Current.account.plan.name %></strong> plan which includes unlimited cards, unlimited users, and <%= storage_to_human_size(Current.account.plan.storage_limit) %> of storage.
<% if subscription.plan == Plan.paid && !(Current.account.nearing_plan_storage_limit? || Current.account.exceeding_storage_limit?) %>
<%= button_to "Upgrade", account_subscription_upgrade_path, class: "btn btn--plain settings-subscription__link txt-link", form: { class: "flex-inline flex-wrap", data: { turbo: false } } %>
to <%= storage_to_human_size(Plan.paid_with_extra_storage.storage_limit) %> storage for <%= format_currency(Plan.paid_with_extra_storage.price - Plan.paid.price) %>/month more.
<% elsif subscription.plan == Plan.paid_with_extra_storage && !Current.account.exceeding_storage_limit? %>
<%= button_to "Downgrade", account_subscription_downgrade_path, class: "btn btn--plain settings-subscription__link txt-link", form: { class: "flex-inline flex-wrap", data: { turbo: false } } %>
to <%= storage_to_human_size(Plan.paid.storage_limit) %> storage.
<% end %>
</div>
@@ -1,11 +0,0 @@
<% if Current.user.admin? && !Current.account.comped? %>
<section class="panel panel--wide settings-subscription__panel shadow center margin-block-double hide-on-native">
<h2 id="subscription" class="divider settings-subscription__divider txt-small txt-uppercase margin-none">Subscription</h2>
<% if Current.account.plan.free? %>
<%= render "account/settings/free_plan" %>
<% else %>
<%= render "account/settings/paid_plan" %>
<% end %>
</section>
<% end %>
@@ -1,9 +0,0 @@
<div class="settings-subscription__notch card-perma__notch card-perma__notch--bottom">
<% if Current.user.admin? %>
<%= render "account/subscriptions/notices/admin_exceeding_card_limit" if Current.account.exceeding_card_limit? %>
<%= render "account/subscriptions/notices/admin_exceeding_storage_limit" if Current.account.exceeding_storage_limit? %>
<% else %>
<%= render "account/subscriptions/notices/user_exceeding_card_limit" if Current.account.exceeding_card_limit? %>
<%= render "account/subscriptions/notices/user_exceeding_storage_limit" if Current.account.exceeding_storage_limit? %>
<% end %>
</div>
@@ -1,4 +0,0 @@
<strong>Youve used your <u><%= Plan.free.card_limit %></u> free cards.</strong>
<%= link_to account_settings_path(anchor: "subscription"), class: "btn settings-subscription__button" do %>
<span>Upgrade to Unlimited</span>
<% end %>
@@ -1,4 +0,0 @@
<strong>Youve run out of <%= Current.account.plan.free? ? "free storage" : "storage" %>.</strong>
<%= link_to account_settings_path(anchor: "subscription"), class: "btn settings-subscription__button" do %>
<span>Upgrade to get more</span>
<% end %>
@@ -1,3 +0,0 @@
<div class="pad-inline pad-block-half">
<strong>This account has used <u><%= Plan.free.card_limit %></u> free cards. Upgrade to get more.</strong>
</div>
@@ -1,3 +0,0 @@
<div class="pad-inline pad-block-half">
<strong>This account has run out of <%= Current.account.plan.free? ? "free storage" : "storage" %>. Upgrade to get more.</strong>
</div>
@@ -1,9 +0,0 @@
<% @page_title = "Thank you" %>
<% if @stripe_session&.payment_status == "paid" %>
<section class="panel panel--wide panel--centered center borderless">
<h1 class="txt-x-large font-weight-black margin-none">Thanks for buying Fizzy!</h1>
<p class="txt-medium margin-none-block-start">Your payment was successful. Youre now on the <strong><%= Current.account.plan.name %></strong> plan.</p>
<%= link_to "Done", account_settings_path, class: "btn settings-subscription__button txt-medium" %>
</section>
<% end %>
@@ -1,63 +0,0 @@
<% @page_title = "Account admin" %>
<% content_for :header do %>
<div class="header__actions header__actions--start">
<%= back_link_to "Accounts", saas.admin_accounts_path, "keydown.left@document->hotkey#click keydown.esc@document->hotkey#click" %>
</div>
<% end %>
<div class="panel panel--wide shadow center">
<div class="flex flex-column gap txt-medium">
<h1 class="txt-x-large font-weight-black margin-none">Edit Account <%= @account.external_account_id %></h1>
<%= form_with model: @account, url: saas.admin_account_path(@account.external_account_id), class: "flex flex-column gap" do |form| %>
<table class="txt-align-start">
<tr>
<td class="txt-subtle">Account Name:</td>
<td class="margin-inline-start-none"><%= @account.name %></td>
</tr>
<tr>
<td class="txt-subtle">Subscription plan:</td>
<td class="margin-inline-start-none"><%= @account.plan.name %></td>
</tr>
<tr>
<td class="txt-subtle">Actual card count:</td>
<td class="margin-inline-start-none"><%= @account.cards_count %> cards </td>
</tr>
<tr>
<td class="txt-subtle">Actual bytes used:</td>
<td class="margin-inline-start-none"><%= storage_to_human_size(@account.bytes_used) %></td>
</tr>
<tr>
<td>
<%= form.label :card_count, class: "txt-subtle" do %>
<span>Override card count</span><br><%= "(Exceeds plan limit)" if @account.exceeding_card_limit? %>
<% end %>
</td>
<td><%= form.number_field :card_count, value: @account.overridden_limits&.card_count, class: "input" %></td>
</tr>
<tr>
<td>
<%= form.label :bytes_used, class: "txt-subtle" do %>
<span>Override bytes used</span><br><%= "(Exceeds plan limit)" if @account.exceeding_storage_limit? %>
<% end %>
</td>
<td><%= form.number_field :bytes_used, value: @account.overridden_limits&.bytes_used, class: "input" %></td>
</tr>
</table>
<button type="submit" class="btn btn--link full-width">Save changes</button>
<% end %>
<% if @account.overridden_limits %>
<%= button_to "Reset limits", saas.admin_account_overridden_limits_path(@account.external_account_id), method: :delete, class: "btn btn--negative full-width" %>
<% end %>
<% if @account.comped? %>
<%= button_to "Uncomp account", saas.admin_account_billing_waiver_path(@account.external_account_id), method: :delete, class: "btn btn--plain txt-negative txt-underline" %>
<% else %>
<%= button_to "Comp account", saas.admin_account_billing_waiver_path(@account.external_account_id), method: :post, class: "btn btn--plain txt-positive txt-underline" %>
<% end %>
</div>
</div>
@@ -1,13 +0,0 @@
<div class="panel panel--centered">
<div class="flex flex-column gap txt-medium">
<h1 class="txt-x-large font-weight-black margin-none">Find an account</h1>
<%= form_with url: saas.admin_account_search_path, class: "flex flex-column gap" do |form| %>
<%= form.text_field :q, placeholder: "Account ID", autofocus: true, class: "input" %>
<%= form.submit "Search", class: "btn btn--link" %>
<% end %>
</div>
</div>
-26
View File
@@ -33,32 +33,6 @@
</div>
</div>
<div class="flex flex-column gap-half">
<header>
<h2 class="divider txt-medium margin-block-start">Accounts Subscribed</h2>
</header>
<div class="flex gap-half">
<div class="flex flex-column gap-quarter flex-item-grow">
<div class="txt-x-small">Total</div>
<div class="txt-large">
<strong><%= @paid_accounts_total %></strong>
</div>
</div>
<div class="flex flex-column gap-quarter flex-item-grow">
<div class="txt-x-small">7 days</div>
<div class="txt-large">
<strong><%= @paid_accounts_last_7_days %></strong>
</div>
</div>
<div class="flex flex-column gap-quarter flex-item-grow">
<div class="txt-x-small">24 hours</div>
<div class="txt-large">
<strong><%= @paid_accounts_last_24_hours %></strong>
</div>
</div>
</div>
</div>
<div class="flex flex-column gap-half">
<header>
<h2 class="divider txt-medium margin-block-start">Identities Created</h2>
@@ -1,5 +0,0 @@
<% if Current.account.exceeding_limits? %>
<%= render "account/subscriptions/upgrade" %>
<% else %>
<%= render "cards/container/footer/create", card: card %>
<% end %>
@@ -1,9 +0,0 @@
<div class="settings_subscription__warning full-width pad-inline center margin-block-half">
<% if Current.user.admin? %>
<%= render "cards/container/footer/saas/notices/admin_nearing_card_limit" if Current.account.nearing_plan_cards_limit? %>
<%= render "cards/container/footer/saas/notices/admin_nearing_storage_limit" if Current.account.nearing_plan_storage_limit? %>
<% else %>
<%= render "cards/container/footer/saas/notices/user_nearing_card_limit" if Current.account.nearing_plan_cards_limit? %>
<%= render "cards/container/footer/saas/notices/user_nearing_storage_limit" if Current.account.nearing_plan_storage_limit? %>
<% end %>
</div>
@@ -1 +0,0 @@
Youve used <%= Current.account.billed_cards_count %> out of <%= Plan.free.card_limit %> free cards. <strong><%= link_to "Upgrade to unlimited", account_settings_path(anchor: "subscription") %></strong>.
@@ -1 +0,0 @@
Youve used <%= storage_to_human_size(Current.account.billed_bytes_used) %> out of <%= storage_to_human_size(Current.account.plan.storage_limit) %> <%= Current.account.plan.free? ? "free storage" : "storage" %>. <strong><%= link_to "Upgrade to get more", account_settings_path(anchor: "subscription") %></strong>.
@@ -1 +0,0 @@
This account has used <%= Current.account.billed_cards_count %> out of <%= Plan.free.card_limit %> free cards. Upgrade soon.
@@ -1 +0,0 @@
This account has used <%= storage_to_human_size(Current.account.billed_bytes_used) %> out of <%= storage_to_human_size(Current.account.plan.storage_limit) %> <%= Current.account.plan.free? ? "free storage" : "storage" %>. Upgrade soon.