Files
ubicloud/views/project/billing.erb

359 lines
15 KiB
Plaintext

<% @page_title = "Project Billing"
if @project.billing_info
@invoices = Serializers::Invoice.serialize(@project.invoices.prepend(@project.current_invoice))
end
@usage_alerts = Serializers::UsageAlert.serialize(@project.usage_alerts_dataset.eager(:user)) %>
<% if billing_info = @project.billing_info %>
<%== part(
"components/page_header",
breadcrumbs: [%w[Projects /project], [@project_data[:name], @project_data[:path]], %w[Billing #]]
) %>
<div class="grid gap-6">
<!-- Summary -->
<div>
<dl class="grid grid-cols-2 gap-5 <%= (@project_data[:discount] > 0) ? "sm:grid-cols-4" : "sm:grid-cols-3" %>">
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500">Current Usage</dt>
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900"><%= @invoices.first[:subtotal] %></dd>
</div>
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500">Last Month</dt>
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900"><%= (@invoices.count > 1) ? @invoices[1][:subtotal] : "-" %></dd>
</div>
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500">Remaining Credit</dt>
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900"><%= "$%0.02f" % @project_data[:credit] %></dd>
</div>
<% if @project_data[:discount] > 0 %>
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6">
<dt class="truncate text-sm font-medium text-gray-500">Discount</dt>
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900"><%= @project_data[:discount] %>%</dd>
</div>
<% end %>
</dl>
</div>
<!-- Billing Info Update Card -->
<% stripe_data = billing_info.stripe_data %>
<div class="md:flex md:items-center md:justify-between pb-1 lg:pb-2">
<div class="min-w-0 flex-1">
<h3 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-2xl sm:tracking-tight">
Billing Details
</h3>
</div>
</div>
<div class="overflow-hidden rounded-lg shadow ring-1 ring-black ring-opacity-5 bg-white divide-y divide-gray-200">
<form action="<%= "#{@project_data[:path]}/billing" %>" method="POST">
<%== csrf_tag("#{@project_data[:path]}/billing") %>
<div class="px-4 py-5 sm:p-6">
<div class="space-y-12">
<div>
<div class="mt-6 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-8">
<div class="sm:col-span-4">
<%== part(
"components/form/text",
name: "name",
label: "Billing Name",
value: stripe_data["name"],
attributes: {
required: true
}
) %>
</div>
<div class="sm:col-span-4">
<%== part(
"components/form/text",
name: "email",
label: "Billing Email",
value: stripe_data["email"],
attributes: {
required: true
}
) %>
</div>
<div class="sm:col-span-4 md:col-span-2">
<%== part(
"components/form/select",
name: "country",
label: "Country",
placeholder: "Select a Country",
selected: stripe_data["country"],
options:
ISO3166::Country
.all
.reject { Config.sanctioned_countries.include?(it.alpha2) }
.sort_by(&:common_name)
.map { |c| [c.alpha2, c.common_name] }
.to_h
.to_a,
attributes: {
required: true
}
) %>
</div>
<div class="sm:col-span-4 md:col-span-2">
<%== part("components/form/text", name: "state", label: "State", value: stripe_data["state"]) %>
</div>
<div class="sm:col-span-4 md:col-span-2">
<%== part("components/form/text", name: "city", label: "City", value: stripe_data["city"]) %>
</div>
<div class="sm:col-span-4 md:col-span-2">
<%== part("components/form/text", name: "postal_code", label: "Postal Code", value: stripe_data["postal_code"]) %>
</div>
<div class="col-span-full">
<%== part(
"components/form/textarea",
name: "address",
label: "Address",
value: stripe_data["address"],
attributes: {
required: true
}
) %>
</div>
<div class="sm:col-span-4">
<%== part(
"components/form/text",
name: "tax_id",
label: billing_info.country&.in_eu_vat? ? "VAT ID" : "Tax ID",
value: stripe_data["tax_id"]
) %>
<% if billing_info.country&.in_eu_vat? %>
<div class="mt-2 text-sm text-gray-500">
<% if billing_info.country.alpha2 == "NL" %>
21% VAT is collected from customers located in the Netherlands.
<% elsif stripe_data["tax_id"].to_s.strip.empty? %>
EU registered business can enter their VAT ID to remove VAT from future invoices.
<% else %>
VAT subject to reverse charge.
<% end %>
</div>
<% end %>
</div>
<div class="sm:col-span-4">
<%== part("components/form/text", name: "company_name", label: "Company Name", value: stripe_data["company_name"]) %>
</div>
</div>
</div>
</div>
</div>
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-end gap-x-6">
<%== part("components/form/submit_button", text: "Update") %>
</div>
</div>
</form>
</div>
<!-- Payment Methods Card -->
<%== part(
"components/table_card",
title: "Payment Methods",
right_items: [
part("components/button", text: "Add Payment Method", link: "#{@project_data[:path]}/billing/payment-method/create")
],
rows:
billing_info.payment_methods.map do |pm|
[
[
"#{pm.stripe_data["brand"].capitalize} ending in #{pm.stripe_data["last4"]}",
"Expires #{pm.stripe_data["exp_month"]}/#{pm.stripe_data["exp_year"]}",
"Added on #{pm.created_at.strftime("%B %d, %Y")}",
[
"delete_button",
{
component: {
url: "#{@project_data[:path]}/billing/payment-method/#{pm.ubid}?project_id=#{@project.ubid}",
csrf_url: "#{@project_data[:path]}/billing/payment-method/#{pm.ubid}",
confirmation: pm.stripe_data["last4"]
},
extra_class: "flex justify-end"
}
]
],
{ id: "payment-method-#{pm.ubid}" }
]
end,
empty_state: "No payment methods. Add new payment method to able create resources in project."
) %>
<%== part("components/discount_code") %>
<!-- Invoices -->
<div>
<div class="md:flex md:items-center md:justify-between pb-1 lg:pb-2">
<div class="min-w-0 flex-1">
<h3 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-2xl sm:tracking-tight">
Invoices
</h3>
</div>
</div>
<div class="overflow-hidden rounded-lg shadow ring-1 ring-black ring-opacity-5 bg-white divide-y divide-gray-200">
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Invoice</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Amount</th>
<th scope="col" class="py-3.5 pl-3 pr-4 sm:pr-6 text-left text-sm font-semibold text-gray-900">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
<% @invoices.each do |inv| %>
<tr id="invoice-<%= inv[:ubid]%>">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6" scope="row">
<a
href="<%= @project_data[:path] + "/billing" + inv[:path] %>"
target="_blank"
class="text-orange-600 hover:text-orange-700"
>
<%= inv[:name] %>
</a>
<span class="text-xs text-gray-400 italic">
<% if inv[:invoice_number] %>
#<%= inv[:invoice_number] %>
<% else %>
(not finalized)
<% end %>
</span>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<%= inv[:total] %>
<% if inv[:total] != inv[:subtotal] %>
<span class="text-xs italic">(<%= inv[:subtotal] %>)</span>
<% end %>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<%= inv[:status] %>
</td>
</tr>
<% end %>
<% if @invoices.count == 1 %>
<tr>
<td colspan="3">
<div class="text-center text-lg p-4">No invoices finalized yet. Invoice for the current month will be created on the first day of next
month.</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<% else %>
<form action="<%= "#{@project_data[:path]}/billing" %>" method="POST">
<%== csrf_tag("#{@project_data[:path]}/billing") %>
<%== part(
"components/empty_state",
icon: "hero-banknotes",
title: "No billing information",
description: "Get started by adding new billing information.",
button_title: "Add new billing information"
) %>
</form>
<br/>
<%== part("components/discount_code") %>
<% end %>
<br/>
<!-- Usage alerts -->
<div>
<div class="md:flex md:items-center md:justify-between pb-1 lg:pb-2">
<div class="min-w-0 flex-1">
<h3 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-2xl sm:tracking-tight">
Usage Alerts
</h3>
</div>
</div>
<div class="grid gap-6">
<div class="overflow-hidden rounded-lg shadow ring-1 ring-black ring-opacity-5 bg-white divide-y divide-gray-200">
<!-- Add usage alert -->
<div class="px-4 py-5 sm:p-6">
<form action="<%= "#{@project_data[:path]}/usage-alert" %>" role="form" method="POST">
<%== csrf_tag("#{@project_data[:path]}/usage-alert") %>
<div class="space-y-4">
<div>
<h2 class="text-lg font-medium leading-6 text-gray-900">Add new usage alert</h2>
<p class="mt-1 text-sm text-gray-500">
Usage alerts will be sent to your email address. If you want to send alerts to specific email
addresses, you need to log in with that email address.
<br/>
<br/>
Please note that alerts are only for informational purposes and no action is taken automatically.
</p>
</div>
<div class="grid grid-cols-12 gap-6">
<div class="col-span-5 sm:col-span-3">
<%== part(
"components/form/text",
name: "alert_name",
label: "Alert Name",
attributes: {
required: true,
placeholder: "Alert Name"
},
extra_class: "pr-3"
) %>
</div>
<div class="col-span-5 sm:col-span-3">
<%== part(
"components/form/text",
name: "limit",
label: "Limit",
type: "number",
attributes: {
required: true,
placeholder: "100"
},
extra_class: "pr-3"
) %>
</div>
<div class="col-span-2 sm:col-span-3 flex justify-begin items-end">
<%== part("components/form/submit_button", text: "Add") %>
</div>
</div>
</div>
</form>
</div>
<!-- List alerts -->
<% if @usage_alerts.count > 0 %>
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-4">Name</th>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-4">Limit</th>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-4">E-mail</th>
<th scope="col" class="py-3.5 pl-3 pr-4 sm:pr-4">
<span class="sr-only">Remove</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
<% @usage_alerts.each do |alert| %>
<tr id="alert-<%= alert[:ubid]%>" class="whitespace-nowrap text-sm font-medium">
<td class="py-4 pl-4 pr-3 text-gray-900 sm:pl-6" scope="row"><%= alert[:name] %></td>
<td class="py-4 pl-4 pr-3 text-gray-900 sm:pl-6" scope="row"><%= alert[:limit] %></td>
<td class="py-4 pl-4 pr-3 text-gray-900 sm:pl-6" scope="row"><%= alert[:email] %></td>
<td class="py-4 pl-3 pr-4 text-right sm:pr-6">
<%== part(
"components/delete_button",
text: "Remove",
url: "#{@project_data[:path]}/usage-alert/#{alert[:ubid]}",
confirmation: alert[:name]
) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="text-center p-4">
No usage alerts...
</div>
<% end %>
</div>
</div>
</div>