Skip to content

Commit

Permalink
Merge pull request #89 from goinvo/fermion/better-subscription-manage…
Browse files Browse the repository at this point in the history
…ment

Better subscription management
  • Loading branch information
fermion authored Feb 26, 2024
2 parents 322ca99 + 1ef3098 commit ccef2e4
Show file tree
Hide file tree
Showing 70 changed files with 5,246 additions and 5,518 deletions.
2 changes: 1 addition & 1 deletion app/commands/add_user_to_company.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ def send_welcome_email
end

def update_stripe_subscription_count
SyncCustomerSubscriptionCountJob.perform_async(@company.id)
SyncCustomerSubscriptionJob.perform_async(@company.id)
end
end
7 changes: 4 additions & 3 deletions app/commands/create_new_company.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
class CreateNewCompany

attr_reader :email, :user, :registration_id, :company, :user
attr_reader :company_name, :email, :user, :registration_id, :company, :user

def initialize(email:, name:, registration_id:)
def initialize(company_name:, email:, name:, registration_id:)
@company_name = company_name
@email = email
@name = name
@registration_id = registration_id
Expand All @@ -20,7 +21,7 @@ def call
private

def create_company_record
@company = Company.create(name: "#{@name}'s' Company")
@company = Company.create(name: @company_name)
end

def add_initial_owner
Expand Down
30 changes: 30 additions & 0 deletions app/commands/stripe/customer_updated.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Stripe
class CustomerUpdated
def initialize(customer)
@customer = customer
end

def call
# update the customer's default payment method, email, name, etc.
company = Company.find_by(stripe_id: @customer.id)

updates = {
customer_email: @customer.email,
customer_name: @customer.name,
}

if @customer.invoice_settings.default_payment_method.present?
payment_method = Stripe::PaymentMethod.retrieve(@customer.invoice_settings.default_payment_method)
updates = updates.merge(
default_payment_method: @customer.invoice_settings.default_payment_method,
credit_card_brand: payment_method.card.brand,
credit_card_last_four: payment_method.card.last4,
credit_card_exp_month: payment_method.card.exp_month,
credit_card_exp_year: payment_method.card.exp_year
)
end

company.subscription.update(updates)
end
end
end
32 changes: 32 additions & 0 deletions app/commands/stripe/subscription_updated.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Stripe
class SubscriptionUpdated
def initialize(subscription)
@subscription = subscription
end

def call
company = Company.find_by(stripe_id: @subscription.customer)
previous_quantity = company.subscription.quantity

canceled_at = @subscription.canceled_at.present? ? Time.at(@subscription.canceled_at) : nil
company.subscription.assign_attributes(
status: @subscription.status,
trial_end: Time.at(@subscription.trial_end),
stripe_id: @subscription.id,
stripe_price_id: @subscription.items.data.first.price.id,
plan_amount: @subscription.items.data.first.price.unit_amount,
quantity: @subscription.quantity,
item_id: @subscription.items.data.first.id,
current_period_start: Time.at(@subscription.current_period_start),
current_period_end: Time.at(@subscription.current_period_end),
canceled_at: canceled_at
)

company.subscription.save!

if previous_quantity != company.subscription.quantity
BillingMailer.subscription_updated(company, company.subscription.quantity).deliver_later
end
end
end
end

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

<div class="bg-white p-4 mt-4">
<main class="mx-auto max-w-2xl pb-24 sm:px-6 lg:max-w-7xl lg:px-8">

<section aria-labelledby="subscription-heading" class="mt-6">
<h2 id="subscription-heading" class="sr-only">Products purchased</h2>

Expand All @@ -19,7 +18,7 @@
<a href="#">StaffPlan Subscription 🚀</a>
</h3>
<p class="mt-2 text-sm font-medium text-gray-900">
<%= number_to_currency(Money.from_cents(subscription.plan.amount, "USD")) %> USD / month
<%= number_to_currency(Money.from_cents(subscription.plan_amount, "USD")) %> USD / month
</p>
<p class="mt-2 text-sm font-medium text-gray-900">
<%= pluralize(subscription.quantity, "staff member") %>
Expand All @@ -33,14 +32,14 @@

<div class="border-t border-gray-200 px-4 py-6 sm:px-6 lg:p-8">
<h4 class="sr-only">Status</h4>
<p class="text-sm font-medium text-gray-900">Subscription automatically renews on <time datetime="<%= Time.at(subscription.current_period_end).to_date.iso8601 %>"><%= Time.at(subscription.current_period_end).to_date.iso8601 %></time></p>
<p class="text-sm font-medium text-gray-900">Subscription automatically renews on <time datetime="<%= subscription.current_period_end.iso8601 %>"><%= subscription.current_period_end.iso8601 %></time></p>
<div class="mt-6" aria-hidden="true">
<div class="overflow-hidden rounded-full bg-gray-200">
<div class="h-2 rounded-full bg-indigo-600" style="width: <%= subscription_percentage %>"></div>
</div>
<div class="mt-6 hidden grid-cols-2 text-sm font-medium text-gray-600 sm:grid">
<div class="text-indigo-600"><%= Time.at(subscription.current_period_start).to_date.iso8601 %></div>
<div class="text-right"><%= Time.at(subscription.current_period_end).to_date.iso8601 %></div>
<div class="text-indigo-600"><%= subscription.current_period_start.iso8601 %></div>
<div class="text-right"><%= subscription.current_period_end.iso8601 %></div>
</div>
</div>
</div>
Expand All @@ -57,20 +56,20 @@
<div>
<dt class="font-medium text-gray-900">Billing contact</dt>
<dd class="mt-3 text-gray-500">
<span class="block"><%= customer.name %></span>
<span class="block"><%= customer.email %></span>
<span class="block"><%= customer_name %></span>
<span class="block"><%= customer_email %></span>
</dd>
</div>
<div>
<dt class="font-medium text-gray-900">Payment information</dt>
<dd class="-ml-4 -mt-1 flex flex-wrap">
<div class="ml-4 mt-4 flex-shrink-0">
<%= image_tag image_path("credit-cards/#{credit_card.brand}.svg"), style: "height: 24px;", class: "h-6 w-auto" %>
<p class="sr-only"><%= credit_card.brand.capitalize %>></p>
<%= image_tag image_path("credit-cards/#{credit_card_brand}.svg"), style: "height: 24px;", class: "h-6 w-auto" %>
<p class="sr-only"><%= credit_card_brand.capitalize %>></p>
</div>
<div class="ml-4 mt-4">
<p class="text-gray-900">Ending with <%= credit_card.last4 %></p>
<p class="text-gray-600">Expires <%= credit_card.exp_month %> / <%= credit_card.exp_year %></p>
<p class="text-gray-900">Ending with <%= credit_card_last_four %></p>
<p class="text-gray-600">Expires <%= credit_card_exp_month %> / <%= credit_card_exp_year %></p>
</div>
</dd>
</div>
Expand All @@ -91,13 +90,9 @@
<h2 id="cancel-subscription" class="sr-only">Cancel Subscription</h2>

<div class="px-4 py-6 sm:rounded-lg sm:px-6 lg:gap-x-8 lg:px-8 lg:py-8 text-center">
Not happy with StaffPlan? We're sorry to see you go. You can cancel your subscription at any time, no questions asked.
Not happy with StaffPlan? We would be sorry to see you go. You can cancel your subscription at any time, no questions asked.

<%= button_to "Cancel my subscription",
settings_subscription_path,
method: :delete,
data: { turbo_method: :delete },
class: "mt-10 block w-full rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %>
<%= link_to "Manage StaffPlan Subscription", Rails.application.credentials.stripe_customer_portal_url, class: "mt-10 block w-full rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %>
</div>
</section>

Expand Down
52 changes: 52 additions & 0 deletions app/components/settings/billing/subscription_active_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Settings
module Billing
class SubscriptionActiveComponent < ViewComponent::Base
def initialize(company:)
@company = company
end

def subscription
@company.subscription
end

def subscription_percentage
time_today = Time.now.at_beginning_of_day.to_i
start_time = subscription.current_period_start.at_beginning_of_day.to_i
end_time = subscription.current_period_end.at_beginning_of_day.to_i
numerator = time_today - start_time
denominator = end_time - start_time

"#{((numerator.to_f / denominator.to_f) * 100).round}%"
end

def credit_card_brand
subscription.credit_card_brand
end

def credit_card_last_four
subscription.credit_card_last_four
end

def credit_card_exp_month
subscription.credit_card_exp_month
end

def credit_card_exp_year
subscription.credit_card_exp_year
end

def customer_name
subscription.customer_name
end

def customer_email
subscription.customer_email
end

def subtotal
amount = Money.new(subscription.plan_amount, "USD") * subscription.quantity
Money.new(amount, "USD")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<%= render(Settings::TabsComponent.new) %>

<div class="bg-white p-4 mt-4">
<main class="mx-auto max-w-2xl pb-24 sm:px-6 lg:max-w-7xl lg:px-8">
<section aria-labelledby="subscription-heading" class="mt-6">
<h2 id="subscription-heading" class="sr-only">Products purchased</h2>

<div class="space-y-8">
<div class="border-b border-t border-gray-200 bg-white shadow-sm sm:rounded-lg sm:border">
<div class="px-4 py-6 sm:px-6 lg:grid lg:gap-x-8 lg:p-8">
<div class="sm:flex lg:col-span-7">
<div class="aspect-h-1 aspect-w-1 w-full flex-shrink-0 overflow-hidden rounded-lg sm:aspect-none sm:h-40 sm:w-40 hidden md:block">
<%= image_tag "staffplan-logo-notext.png", class: "h-full w-full object-cover object-center sm:h-full sm:w-full" %>
</div>

<div class="mt-6 sm:ml-6 sm:mt-0">
<h3 class="text-base font-medium text-gray-900">
<a href="#">Your StaffPlan subscription has been canceled.</a>
</h3>
<p class="mt-3 text-sm text-gray-500">
You can continue to use StaffPlan until <%= subscription.current_period_end.iso8601 %>. After that, you will lose access to StaffPlan and your account will be in read-only mode.
</p>
</div>
</div>
</div>
</div>
</div>
</section>

<!-- Billing -->
<% if subscription.default_payment_method %>
<section aria-labelledby="billing-heading" class="mt-16">
<h2 id="billing-heading" class="sr-only">Billing Summary</h2>

<div class="bg-gray-100 px-4 py-6 sm:rounded-lg sm:px-6 lg:grid lg:grid-cols-12 lg:gap-x-8 lg:px-8 lg:py-8">
<dl class="grid grid-cols-2 gap-6 text-sm sm:grid-cols-2 md:gap-x-8 lg:col-span-7">
<div>
<dt class="font-medium text-gray-900">Billing contact</dt>
<dd class="mt-3 text-gray-500">
<span class="block"><%= customer_name %></span>
<span class="block"><%= customer_email %></span>
</dd>
</div>
<div>
<dt class="font-medium text-gray-900">Payment information</dt>
<dd class="-ml-4 -mt-1 flex flex-wrap">
<div class="ml-4 mt-4 flex-shrink-0">
<%= image_tag image_path("credit-cards/#{credit_card_brand}.svg"), style: "height: 24px;", class: "h-6 w-auto" %>
<p class="sr-only"><%= credit_card_brand.capitalize %>></p>
</div>
<div class="ml-4 mt-4">
<p class="text-gray-900">Ending with <%= credit_card_last_four %></p>
<p class="text-gray-600">Expires <%= credit_card_exp_month %> / <%= credit_card_exp_year %></p>
</div>
</dd>
</div>
</dl>

<dl class="mt-8 divide-y divide-gray-200 text-sm lg:col-span-5 lg:mt-0">
<div class="flex items-center justify-between pt-4">
<dt class="font-medium text-gray-900">Total monthly charges:</dt>
<dd class="font-medium text-indigo-600">
<%= number_to_currency(subtotal) %>
</dd>
</div>
</dl>
</div>
</section>
<% end %>

<section aria-labelledby="cancel-subscription" class="mt-16">
<% if subscription.can_be_resumed? %>
<div class="px-4 py-6 sm:rounded-lg sm:px-6 lg:gap-x-8 lg:px-8 lg:py-8 text-center">
Changed your mind? You can still manage your StaffPlan subscription until <%= subscription.current_period_end.iso8601 %>. Click the manage button below to renew your subscription.

<%= link_to "Manage StaffPlan Subscription", Rails.application.credentials.stripe_customer_portal_url, class: "mt-10 block w-full rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %>
</div>
<% else %>
<div class="px-4 py-6 sm:rounded-lg sm:px-6 lg:gap-x-8 lg:px-8 lg:py-8 text-center">
We're sorry to see you go! If you've changed your mind, please contact us so that we can resume your subscription.
</div>
<% end %>
</section>

</main>
</div>
52 changes: 52 additions & 0 deletions app/components/settings/billing/subscription_canceled_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Settings
module Billing
class SubscriptionCanceledComponent < ViewComponent::Base
def initialize(company:)
@company = company
end

def subscription
@company.subscription
end

def subscription_percentage
time_today = Time.now.at_beginning_of_day.to_i
start_time = subscription.current_period_start.at_beginning_of_day.to_i
end_time = subscription.current_period_end.at_beginning_of_day.to_i
numerator = time_today - start_time
denominator = end_time - start_time

"#{((numerator.to_f / denominator.to_f) * 100).round}%"
end

def credit_card_brand
subscription.credit_card_brand
end

def credit_card_last_four
subscription.credit_card_last_four
end

def credit_card_exp_month
subscription.credit_card_exp_month
end

def credit_card_exp_year
subscription.credit_card_exp_year
end

def customer_name
subscription.customer_name
end

def customer_email
subscription.customer_email
end

def subtotal
amount = Money.new(subscription.plan_amount, "USD") * subscription.quantity
Money.new(amount, "USD")
end
end
end
end
Loading

0 comments on commit ccef2e4

Please sign in to comment.