Skip to content

Commit

Permalink
Add special support for enterprise plan
Browse files Browse the repository at this point in the history
  • Loading branch information
loicknuchel committed Jun 26, 2024
1 parent 1dcf34e commit 7b7c575
Show file tree
Hide file tree
Showing 19 changed files with 120 additions and 93 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ export FILE_STORAGE_ADAPTER=local
# export SMTP_PASSWORD=
# export SMTP_PORT=

# export SUPPORT_EMAIL=
# export SENDER_EMAIL=
# export CONTACT_EMAIL=
# export SUPPORT_EMAIL=
# export ENTERPRISE_SUPPORT_EMAIL=

# Key features

Expand Down Expand Up @@ -125,6 +127,7 @@ export AUTH_PASSWORD=true
# export STRIPE_PRICE_SOLO_YEARLY=
# export STRIPE_PRICE_TEAM_MONTHLY=
# export STRIPE_PRICE_TEAM_YEARLY=
# export STRIPE_PRODUCT_ENTERPRISE=
# export STRIPE_PRICE_PRO_MONTHLY=

## Clever Cloud Addon
Expand Down
5 changes: 4 additions & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ These are the basic variables you will **need** to set up Azimutt:
- `SMTP_USERNAME` (required)
- `SMTP_PASSWORD` (required)
- `SMTP_PORT` (required)
- `SUPPORT_EMAIL` (optional, default `contact@azimutt.app`): email shown in Azimutt when users need support
- `SENDER_EMAIL` (optional, default `contact@azimutt.app`): email Azimutt will us to send emails
- `CONTACT_EMAIL` (optional, default `contact@azimutt.app`): email shown in Azimutt to reach out
- `SUPPORT_EMAIL` (optional, default `contact@azimutt.app`): email shown in Azimutt when users need support
- `ENTERPRISE_SUPPORT_EMAIL` (optional, default `contact@azimutt.app`): email shown in Azimutt for high priority support


### Key features
Expand Down Expand Up @@ -211,6 +213,7 @@ At least one of authentication methods should be defined:
- `STRIPE_PRICE_SOLO_YEARLY` (required): Stripe price for the yearly solo plan (ex: `price_uJINukB78aAbajUQHy6Ra523`)
- `STRIPE_PRICE_TEAM_MONTHLY` (required): Stripe price for the monthly team plan (ex: `price_uJINukB78aAbajUQHy6Ra523`)
- `STRIPE_PRICE_TEAM_YEARLY` (required): Stripe price for the yearly team plan (ex: `price_uJINukB78aAbajUQHy6Ra523`)
- `STRIPE_PRODUCT_ENTERPRISE` (required): Stripe product for enterprise plan (ex: `prod_eBlQLUZPVprdAo`)
- `STRIPE_PRICE_PRO_MONTHLY` (required): Stripe price for the monthly legacy pro plan (ex: `price_uJINukB78aAbajUQHy6Ra523`)
- `CLEVER_CLOUD` (optional): if `true`, enable auth & hooks for [Clever Cloud Add-on](https://www.clever-cloud.com/doc/extend/add-ons-api)
- `CLEVER_CLOUD_ADDON_ID` (required)
Expand Down
7 changes: 5 additions & 2 deletions backend/config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ config :azimutt,
organization_default_plan: System.get_env("ORGANIZATION_DEFAULT_PLAN"),
global_organization: global_organization,
global_organization_alone: global_organization && System.get_env("GLOBAL_ORGANIZATION_ALONE") == "true",
support_email: System.get_env("SUPPORT_EMAIL") || "contact@azimutt.app",
sender_email: System.get_env("SENDER_EMAIL") || "contact@azimutt.app",
sender_email: System.get_env("SENDER_EMAIL") || Azimutt.config(:azimutt_email),
contact_email: System.get_env("CONTACT_EMAIL") || Azimutt.config(:azimutt_email),
support_email: System.get_env("SUPPORT_EMAIL") || System.get_env("CONTACT_EMAIL") || Azimutt.config(:azimutt_email),
enterprise_support_email: System.get_env("ENTERPRISE_SUPPORT_EMAIL") || System.get_env("SUPPORT_EMAIL") || System.get_env("CONTACT_EMAIL") || Azimutt.config(:azimutt_email),
server_started: DateTime.utc_now()

config :azimutt, Azimutt.Repo,
Expand Down Expand Up @@ -274,6 +276,7 @@ if System.get_env("STRIPE") == "true" do
stripe_price_solo_yearly: System.fetch_env!("STRIPE_PRICE_SOLO_YEARLY"),
stripe_price_team_monthly: System.fetch_env!("STRIPE_PRICE_TEAM_MONTHLY"),
stripe_price_team_yearly: System.fetch_env!("STRIPE_PRICE_TEAM_YEARLY"),
stripe_product_enterprise: System.fetch_env!("STRIPE_PRODUCT_ENTERPRISE"),
stripe_price_pro_monthly: System.fetch_env!("STRIPE_PRICE_PRO_MONTHLY")

config :stripity_stripe,
Expand Down
49 changes: 29 additions & 20 deletions backend/lib/azimutt/organizations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -271,23 +271,7 @@ defmodule Azimutt.Organizations do

def get_subscriptions(%Organization{} = organization) do
StripeSrv.get_subscriptions(organization.stripe_customer_id)
|> Result.map(fn subs ->
subs.data
|> Enum.map(fn sub ->
%{
id: sub.id,
customer: sub.customer,
status: sub.status,
promotion_code: sub.promotion_code,
price: sub.plan.id,
product: sub.plan.product,
freq: sub.plan.interval,
quantity: sub.quantity,
cancel_at: if(sub.cancel_at != nil, do: DateTime.from_unix!(sub.cancel_at), else: nil),
created: DateTime.from_unix!(sub.created)
}
end)
end)
|> Result.map(fn subs -> subs.data |> Enum.map(fn sub -> subscription_to_hash(sub) end) end)
end

def allow_table_color(%Organization{} = organization, tweet_url) when is_binary(tweet_url) do
Expand Down Expand Up @@ -470,15 +454,20 @@ defmodule Azimutt.Organizations do
{:ok, %{plan: plan, plan_freq: "monthly", plan_status: "manual", plan_seats: seats}}
end

# credo:disable-for-lines:20 Credo.Check.Refactor.Nesting
defp validate_stripe_plan(customer_id) do
StripeSrv.get_subscriptions(customer_id)
|> Result.map(fn subs ->
if length(subs.data) > 0 do
sub = hd(subs.data)
{plan, freq} = StripeSrv.get_plan(sub.plan.id)
sub = subscription_to_hash(hd(subs.data))
{plan, freq} = StripeSrv.get_plan(sub.product, sub.price)

if ["trialing", "active", "past_due", "unpaid"] |> Enum.member?(sub.status) do
%{plan: plan, plan_freq: freq, plan_status: sub.status, plan_seats: sub.quantity}
if plan == "enterprise" do
%{plan: plan, plan_freq: freq, plan_status: sub.status, plan_seats: sub.metadata.seats || sub.quantity}
else
%{plan: plan, plan_freq: freq, plan_status: sub.status, plan_seats: sub.quantity}
end
else
%{plan: "free", plan_freq: freq, plan_status: sub.status, plan_seats: 1}
end
Expand All @@ -501,4 +490,24 @@ defmodule Azimutt.Organizations do
is_integer(b) -> b
end
end

defp subscription_to_hash(sub) do
%{
id: sub.id,
customer: sub.customer,
status: sub.status,
promotion_code: sub.promotion_code,
price: sub.plan.id,
product: sub.plan.product,
freq: sub.plan.interval,
quantity: sub.quantity,
metadata: %{
seats: if(is_binary(sub.metadata["seats"]), do: String.to_integer(sub.metadata["seats"]), else: nil),
projects: if(is_binary(sub.metadata["projects"]), do: String.to_integer(sub.metadata["projects"]), else: nil),
databases: if(is_binary(sub.metadata["databases"]), do: String.to_integer(sub.metadata["databases"]), else: nil)
},
cancel_at: if(sub.cancel_at != nil, do: DateTime.from_unix!(sub.cancel_at), else: nil),
created: DateTime.from_unix!(sub.created)
}
end
end
24 changes: 12 additions & 12 deletions backend/lib/azimutt/organizations/organization_plan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,19 @@ defmodule Azimutt.Organizations.OrganizationPlan do

def enterprise do
%OrganizationPlan{
id: Azimutt.plans().enterpprise.id,
name: Azimutt.plans().enterpprise.name,
projects: Azimutt.limits().projects.enterpprise,
layouts: Azimutt.limits().project_layouts.enterpprise,
layout_tables: Azimutt.limits().layout_tables.enterpprise,
memos: Azimutt.limits().project_doc.enterpprise,
groups: Azimutt.limits().project_doc.enterpprise,
colors: Azimutt.limits().colors.enterpprise,
id: Azimutt.plans().enterprise.id,
name: Azimutt.plans().enterprise.name,
projects: Azimutt.limits().projects.enterprise,
layouts: Azimutt.limits().project_layouts.enterprise,
layout_tables: Azimutt.limits().layout_tables.enterprise,
memos: Azimutt.limits().project_doc.enterprise,
groups: Azimutt.limits().project_doc.enterprise,
colors: Azimutt.limits().colors.enterprise,
local_save: true,
private_links: Azimutt.limits().project_share.enterpprise,
sql_export: Azimutt.limits().schema_export.enterpprise,
db_analysis: Azimutt.limits().analysis.enterpprise != "preview",
db_access: Azimutt.limits().data_exploration.enterpprise,
private_links: Azimutt.limits().project_share.enterprise,
sql_export: Azimutt.limits().schema_export.enterprise,
db_analysis: Azimutt.limits().analysis.enterprise != "preview",
db_access: Azimutt.limits().data_exploration.enterprise,
streak: 0
}
end
Expand Down
4 changes: 2 additions & 2 deletions backend/lib/azimutt/services/cockpit_srv.ex
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ defmodule Azimutt.Services.CockpitSrv do
organization_default_plan: System.get_env("ORGANIZATION_DEFAULT_PLAN"),
global_organization: System.get_env("GLOBAL_ORGANIZATION"),
global_organization_alone: System.get_env("GLOBAL_ORGANIZATION_ALONE"),
support: System.get_env("SUPPORT_EMAIL"),
sender: System.get_env("SENDER_EMAIL"),
support: System.get_env("SUPPORT_EMAIL"),
gateway: System.get_env("GATEWAY_URL"),
auth:
%{
Expand Down Expand Up @@ -231,7 +231,7 @@ defmodule Azimutt.Services.CockpitSrv do
end

defp contact_us do
"contact us at <a href=\"mailto:#{Azimutt.config(:azimutt_email)}\" class=\"font-bold underline\">#{Azimutt.config(:azimutt_email)}</a>"
"contact us at <a href=\"mailto:#{Azimutt.config(:contact_email)}\" class=\"font-bold underline\">#{Azimutt.config(:contact_email)}</a>"
end

defp set_error_message(message) do
Expand Down
7 changes: 4 additions & 3 deletions backend/lib/azimutt/services/stripe_srv.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ defmodule Azimutt.Services.StripeSrv do
end

def create_session(%{customer: customer, success_url: success_url, cancel_url: cancel_url, price_id: price_id, quantity: quantity, free_trial: free_trial}) do
# https://docs.stripe.com/api/checkout/sessions/create
if stripe_configured?() do
Stripe.Session.create(%{
mode: "subscription",
Expand All @@ -89,6 +90,7 @@ defmodule Azimutt.Services.StripeSrv do
},
else: %{}
),
automatic_tax: %{enabled: true},
payment_method_collection: "if_required",
allow_promotion_codes: true,
success_url: success_url,
Expand Down Expand Up @@ -120,15 +122,14 @@ defmodule Azimutt.Services.StripeSrv do
end
end

def get_plan(price) do
def get_plan(product, price) do
cond do
price == Azimutt.config(:stripe_price_solo_monthly) -> {"solo", "monthly"}
price == Azimutt.config(:stripe_price_solo_yearly) -> {"solo", "yearly"}
price == Azimutt.config(:stripe_price_team_monthly) -> {"team", "monthly"}
price == Azimutt.config(:stripe_price_team_yearly) -> {"team", "yearly"}
price == Azimutt.config(:stripe_price_pro_monthly) -> {"pro", "monthly"}
# TODO: remove, it's useful in dev because I used several team plans :/
true -> {"team", "yearly"}
product == Azimutt.config(:stripe_product_enterprise) -> {"enterprise", "yearly"}
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ defmodule AzimuttWeb.OrganizationBillingController do
Tracking.subscribe_abort(current_user, organization)

conn
|> put_flash(:info, "Did you changed your mind? Let us know if you need clarifications: #{Azimutt.config(:azimutt_email)}")
|> put_flash(:info, "Did you changed your mind? Let us know if you need clarifications: #{Azimutt.config(:contact_email)}")
|> redirect(to: Routes.organization_billing_path(conn, :index, organization, source: "billing-cancel"))
end
end
2 changes: 1 addition & 1 deletion backend/lib/azimutt_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ defmodule AzimuttWeb.Router do
description: "API Documentation for Azimutt Backend",
contact: %{
name: "Azimutt",
email: Azimutt.config(:azimutt_email)
email: Azimutt.config(:contact_email)
}
},
basePath: "/api/v1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path>
</svg>
</a>
<a href={"mailto:#{Azimutt.config(:azimutt_email)}"} rel="noopener noreferrer" aria-label="Email" class="p-2 rounded-md">
<a href={"mailto:#{Azimutt.config(:contact_email)}"} rel="noopener noreferrer" aria-label="Email" class="p-2 rounded-md">
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 fill-current">
<path d="M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm0 48v40.805c-22.422 18.259-58.168 46.651-134.587 106.49-16.841 13.247-50.201 45.072-73.413 44.701-23.208.375-56.579-31.459-73.413-44.701C106.18 199.465 70.425 171.067 48 152.805V112h416zM48 400V214.398c22.914 18.251 55.409 43.862 104.938 82.646 21.857 17.205 60.134 55.186 103.062 54.955 42.717.231 80.509-37.199 103.053-54.947 49.528-38.783 82.032-64.401 104.947-82.653V400H48z"></path>
</svg>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<% {plan_id, freq} = Azimutt.Services.StripeSrv.get_plan(@subscription.price) %>
<% {plan_id, freq} = Azimutt.Services.StripeSrv.get_plan(@subscription.product, @subscription.price) %>
<% plan = Azimutt.plans()[String.to_atom(plan_id)] %>
<%= plan.name %> plan, <%= freq %>:
<%= @subscription.status %>
Expand Down
80 changes: 44 additions & 36 deletions backend/lib/azimutt_web/templates/organization/billing.html.heex
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
<div class="px-3 py-12">
<h2 class="text-3xl font-semibold leading-7 text-indigo-600 text-center">Billing</h2>
<p class="mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600">
Thank you for using Azimutt.
You are currently on <span title={@organization.plan_status}><%= AzimuttWeb.LayoutView.plan_badge(@organization.plan) %></span>
<%= if @organization.plan_seats > 1 do %>
with <b><%= @organization.plan_seats %> seats</b>.
<% end %>
<br>
<% max_seats = Azimutt.limits().users[String.to_atom(@organization.plan)] %>
<%= if max_seats == nil || max_seats > 1 do %>
Change you seats using the "Manage your subscription" button.
<% end %>
</p>
<div class="max-w-lg mx-auto">
<%= if length(@subscriptions) > 1 do %>
<div class="mt-3 rounded-md bg-yellow-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<Icon.exclamation_triangle kind="mini" class="h-5 w-5 text-yellow-400" />
</div>
<div class="ml-3">
<h3 class="my-0 text-sm font-medium text-yellow-800">You have <%= length(@subscriptions) %> subscriptions.</h3>
<div class="mt-2 text-sm text-yellow-700">
Having several subscriptions is not expected.<br>
Click on "Manage your subscription" below and cancel the bad one.
<%= if @organization.plan == "enterprise" do %>
<p class="mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600">
Thank you for using Azimutt.
You are currently on <span title={@organization.plan_status}><%= AzimuttWeb.LayoutView.plan_badge(@organization.plan) %></span><br>
For any change or question, please contact <a href={"mailto:#{Azimutt.config(:enterprise_support_email)}"} class="underline text-indigo-600 hover:text-indigo-900"><%= Azimutt.config(:enterprise_support_email) %></a>.
</p>
<% else %>
<p class="mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600">
Thank you for using Azimutt.
You are currently on <span title={@organization.plan_status}><%= AzimuttWeb.LayoutView.plan_badge(@organization.plan) %></span>
<%= if @organization.plan_seats > 1 do %>
with <b><%= @organization.plan_seats %> seats</b>.
<% end %>
<br>
<% max_seats = Azimutt.limits().users[String.to_atom(@organization.plan)] %>
<%= if max_seats == nil || max_seats > 1 do %>
Change you seats using the "Manage your subscription" button.
<% end %>
</p>
<div class="max-w-lg mx-auto">
<%= if length(@subscriptions) > 1 do %>
<div class="mt-3 rounded-md bg-yellow-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<Icon.exclamation_triangle kind="mini" class="h-5 w-5 text-yellow-400" />
</div>
<div class="mt-2 text-sm text-yellow-700">
Your subscriptions:
<ul role="list" class="list-disc space-y-1 pl-5">
<%= for subscription <- @subscriptions do %>
<li><%= render "_subscription_show.html", subscription: subscription %></li>
<% end %>
</ul>
<div class="ml-3">
<h3 class="my-0 text-sm font-medium text-yellow-800">You have <%= length(@subscriptions) %> subscriptions.</h3>
<div class="mt-2 text-sm text-yellow-700">
Having several subscriptions is not expected.<br>
Click on "Manage your subscription" below and cancel the bad one.
</div>
<div class="mt-2 text-sm text-yellow-700">
Your subscriptions:
<ul role="list" class="list-disc space-y-1 pl-5">
<%= for subscription <- @subscriptions do %>
<li><%= render "_subscription_show.html", subscription: subscription %></li>
<% end %>
</ul>
</div>
</div>
</div>
</div>
</div>
<% end %>
<%= link "Manage your subscription", to: Routes.organization_billing_path(@conn, :edit, @organization.id), method: :post, class: "mt-6 inline-flex items-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm" %>
<%= link "Refresh plan", to: Routes.organization_billing_path(@conn, :refresh, @organization.id), method: :post, title: "If your plan is out of sync, you can manually refresh it by clicking here.", class: "mt-3 block text-sm underline" %>
</div>
<% end %>
<%= link "Manage your subscription", to: Routes.organization_billing_path(@conn, :edit, @organization.id), method: :post, class: "mt-6 inline-flex items-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm" %>
<%= link "Refresh plan", to: Routes.organization_billing_path(@conn, :refresh, @organization.id), method: :post, title: "If your plan is out of sync, you can manually refresh it by clicking here.", class: "mt-3 block text-sm underline" %>
</div>
<% end %>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<b>Recurrent Azimutt user?</b>
Choose the plan that suit you more.<br>
More details on <a href={Routes.website_path(@conn, :pricing)} target="_blank" rel="noopener noreferrer" class="underline">pricing page</a>.
Don't hesitate to <a href={"mailto:#{Azimutt.config(:azimutt_email)}"} class="underline">reach out</a> for any question.
Don't hesitate to <a href={"mailto:#{Azimutt.config(:contact_email)}"} class="underline">reach out</a> for any question.
</p>
<div class="mt-16 flex justify-center">
<fieldset aria-label="Payment frequency">
Expand Down
Loading

0 comments on commit 7b7c575

Please sign in to comment.