Billing: fatture manuali, profilo obbligatorio e piani Stripe mensile/annuale.
Rimuove link al portale Stripe in area cliente, aggiunge flusso admin per PDF fattura e email, blocca checkout senza dati di fatturazione, allinea prezzi a €4,90/€39,90 e €9,90/€69,90, locale italiano e documentazione deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
45
backend/app/views/admin/billing_invoices/edit.html.erb
Normal file
45
backend/app/views/admin/billing_invoices/edit.html.erb
Normal file
@@ -0,0 +1,45 @@
|
||||
<h1>Fattura <%= @invoice.number %> — <%= @club.name %></h1>
|
||||
<p style="color:#666">
|
||||
Stato: <strong><%= @invoice.status %></strong>
|
||||
<% if @invoice.emailed_at.present? %>
|
||||
· Inviata il <%= @invoice.emailed_at.in_time_zone.strftime("%d/%m/%Y %H:%M") %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<div class="card">
|
||||
<%= form_with model: @invoice, url: admin_club_billing_invoice_path(@club, @invoice), multipart: true, method: :patch do |f| %>
|
||||
<%= f.label :number, "Numero fattura" %>
|
||||
<%= f.text_field :number, required: true, disabled: @invoice.status.in?(%w[sent]) %>
|
||||
|
||||
<%= f.label :issued_on, "Data emissione" %>
|
||||
<%= f.date_field :issued_on, required: true, disabled: @invoice.status.in?(%w[sent]) %>
|
||||
|
||||
<%= label_tag :amount_euros, "Importo (€)" %>
|
||||
<%= number_field_tag "billing_invoice[amount_euros]",
|
||||
@invoice.amount_cents / 100.0,
|
||||
step: 0.01, min: 0.01, required: true, disabled: @invoice.status.in?(%w[sent]) %>
|
||||
|
||||
<% if @payment %>
|
||||
<p style="color:#888;font-size:0.9rem">Pagamento: <%= @payment.display_description %> — <%= @payment.formatted_amount %></p>
|
||||
<% end %>
|
||||
|
||||
<%= f.label :pdf, "PDF fattura" %>
|
||||
<% if @invoice.pdf.attached? %>
|
||||
<p style="color:#888">PDF già caricato: <%= @invoice.pdf.filename %></p>
|
||||
<% end %>
|
||||
<%= f.file_field :pdf, accept: "application/pdf" %>
|
||||
|
||||
<%= f.label :notes, "Note interne" %>
|
||||
<%= f.text_area :notes, rows: 2 %>
|
||||
|
||||
<div style="margin-top:16px;display:flex;gap:10px;flex-wrap:wrap">
|
||||
<% unless @invoice.status.in?(%w[sent]) %>
|
||||
<%= f.submit "Salva bozza", class: "btn btn-secondary" %>
|
||||
<%= f.submit "Emetti e invia via email", class: "btn btn-primary",
|
||||
data: { turbo_confirm: "Inviare la fattura a #{@club.billing_email}?" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<p><%= link_to "← Pagamenti e fatture", admin_club_billing_invoices_path(@club) %></p>
|
||||
@@ -1,10 +1,56 @@
|
||||
<h1>Fatture — <%= @club.name %></h1>
|
||||
<p><%= link_to "← Dashboard", admin_root_path %> · <%= link_to "Nuova fattura", new_admin_club_billing_invoice_path(@club), class: "btn btn-primary" %></p>
|
||||
<h1>Fatturazione — <%= @club.name %></h1>
|
||||
<p>
|
||||
<%= link_to "← Dashboard", admin_root_path %>
|
||||
· <%= link_to "Nuova fattura (senza pagamento)", new_admin_club_billing_invoice_path(@club), class: "btn btn-secondary" %>
|
||||
</p>
|
||||
|
||||
<h2 style="margin-top:24px;font-size:1.1rem">Pagamenti</h2>
|
||||
<% if @payments.any? %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Descrizione</th>
|
||||
<th>Importo</th>
|
||||
<th>Fattura</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @payments.each do |payment| %>
|
||||
<% inv = payment.invoice %>
|
||||
<tr>
|
||||
<td><%= payment.paid_at&.to_date || payment.created_at.to_date %></td>
|
||||
<td><%= payment.display_description %></td>
|
||||
<td><%= payment.formatted_amount %></td>
|
||||
<td>
|
||||
<% if inv %>
|
||||
<%= inv.number %> — <%= inv.status %>
|
||||
<% if inv.pdf.attached? %> (PDF)<% end %>
|
||||
<% else %>
|
||||
—
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if inv %>
|
||||
<%= link_to "Modifica / invia PDF", edit_admin_club_billing_invoice_path(@club, inv) %>
|
||||
<% else %>
|
||||
<%= link_to "Crea fattura", new_admin_club_billing_invoice_path(@club, billing_payment_id: payment.id), class: "btn btn-primary", style: "padding:6px 10px;font-size:0.85rem" %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p>Nessun pagamento registrato per questa società.</p>
|
||||
<% end %>
|
||||
|
||||
<h2 style="margin-top:32px;font-size:1.1rem">Tutte le fatture</h2>
|
||||
<% if @invoices.any? %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr><th>Numero</th><th>Data</th><th>Importo</th><th>Stato</th><th>PDF</th></tr>
|
||||
<tr><th>Numero</th><th>Data</th><th>Importo</th><th>Stato</th><th>Pagamento</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @invoices.each do |inv| %>
|
||||
@@ -13,7 +59,8 @@
|
||||
<td><%= inv.issued_on %></td>
|
||||
<td><%= inv.formatted_amount %></td>
|
||||
<td><%= inv.status %></td>
|
||||
<td><%= inv.pdf.attached? ? "Sì" : "No" %></td>
|
||||
<td><%= inv.billing_payment_id.present? ? "Sì" : "No" %></td>
|
||||
<td><%= link_to "Modifica", edit_admin_club_billing_invoice_path(@club, inv) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<h1>Nuova fattura Aruba — <%= @club.name %></h1>
|
||||
<p style="color:#666">Carica il PDF emesso da Aruba Fatture e collega il pagamento se disponibile.</p>
|
||||
<h1>Nuova fattura — <%= @club.name %></h1>
|
||||
<% if @payment %>
|
||||
<p style="color:#666">Collegata al pagamento del <%= @payment.paid_at&.to_date || @payment.created_at.to_date %> (<%= @payment.formatted_amount %>).</p>
|
||||
<% end %>
|
||||
|
||||
<div class="card">
|
||||
<%= form_with model: @invoice, url: admin_club_billing_invoices_path(@club), multipart: true do |f| %>
|
||||
<%= form_with model: @invoice, url: admin_club_billing_invoices_path(@club) do |f| %>
|
||||
<%= f.hidden_field :billing_payment_id if @payment %>
|
||||
|
||||
<%= f.label :number, "Numero fattura" %>
|
||||
<%= f.text_field :number, required: true %>
|
||||
|
||||
@@ -10,27 +14,19 @@
|
||||
<%= f.date_field :issued_on, required: true %>
|
||||
|
||||
<%= label_tag :amount_euros, "Importo (€)" %>
|
||||
<%= number_field_tag "billing_invoice[amount_euros]", nil, step: 0.01, min: 0.01, required: true %>
|
||||
<%= number_field_tag "billing_invoice[amount_euros]",
|
||||
(@invoice.amount_cents ? @invoice.amount_cents / 100.0 : nil),
|
||||
step: 0.01, min: 0.01, required: true %>
|
||||
|
||||
<%= f.label :status, "Stato" %>
|
||||
<%= f.select :status, Billing::Invoice::STATUSES %>
|
||||
|
||||
<%= f.label :billing_payment_id, "Pagamento collegato (opzionale)" %>
|
||||
<%= f.collection_select :billing_payment_id, @club.billing_payments.recent, :id,
|
||||
->(p) { "#{p.paid_at&.to_date || p.created_at.to_date} — #{p.formatted_amount}" },
|
||||
{ include_blank: "Nessuno" } %>
|
||||
|
||||
<%= f.label :aruba_document_id, "ID documento Aruba (opzionale)" %>
|
||||
<%= f.text_field :aruba_document_id %>
|
||||
|
||||
<%= f.label :pdf, "PDF fattura" %>
|
||||
<%= f.file_field :pdf, accept: "application/pdf", required: true %>
|
||||
|
||||
<%= f.label :notes, "Note interne" %>
|
||||
<%= f.label :notes, "Note interne (opzionale)" %>
|
||||
<%= f.text_area :notes, rows: 2 %>
|
||||
|
||||
<%= f.submit "Registra fattura", class: "btn btn-primary" %>
|
||||
<p style="color:#666;font-size:0.9rem;margin-top:12px">
|
||||
Salva la bozza, poi carica il PDF e inviala al cliente dalla schermata successiva.
|
||||
</p>
|
||||
|
||||
<%= f.submit "Crea bozza fattura", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<p><%= link_to "← Elenco fatture", admin_club_billing_invoices_path(@club) %></p>
|
||||
<p><%= link_to "← Pagamenti e fatture", admin_club_billing_invoices_path(@club) %></p>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<p>Buongiorno,</p>
|
||||
|
||||
<p>in allegato trovi la fattura <strong><%= @invoice.display_number %></strong> del <%= l(@invoice.issued_on, locale: :it, format: :long) %>
|
||||
per un importo di <strong><%= @invoice.formatted_amount %></strong>, relativa alla società <strong><%= @club.billing_legal_name.presence || @club.name %></strong>.</p>
|
||||
|
||||
<p>Puoi scaricarla anche dalla sezione <strong>Abbonamento</strong> del tuo account Match Live TV.</p>
|
||||
|
||||
<p>Grazie,<br>Match Live TV</p>
|
||||
@@ -0,0 +1,9 @@
|
||||
Buongiorno,
|
||||
|
||||
in allegato la fattura <%= @invoice.display_number %> del <%= l(@invoice.issued_on, locale: :it, format: :long) %>
|
||||
(<%= @invoice.formatted_amount %>) per <%= @club.billing_legal_name.presence || @club.name %>.
|
||||
|
||||
È disponibile anche in Abbonamento sul sito Match Live TV.
|
||||
|
||||
Grazie,
|
||||
Match Live TV
|
||||
@@ -6,7 +6,7 @@
|
||||
<title><%= content_for?(:title) ? yield(:title) : "Match Live TV" %></title>
|
||||
<%= render "shared/meta_tags" %>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<link rel="stylesheet" href="/marketing.css?v=31">
|
||||
<link rel="stylesheet" href="/marketing.css?v=32">
|
||||
</head>
|
||||
<body>
|
||||
<%= render "shared/marketing_nav" %>
|
||||
@@ -18,6 +18,6 @@
|
||||
<%= render "shared/marketing_footer" %>
|
||||
<script src="/branding-form.js?v=1" defer></script>
|
||||
<script src="/roster-form.js?v=1" defer></script>
|
||||
<script src="/password-toggle.js?v=1" defer></script>
|
||||
<script src="/password-toggle.js?v=2" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,8 +5,21 @@
|
||||
<h1>Dati di fatturazione</h1>
|
||||
<p style="color:#aaa">Società: <strong><%= @club.name %></strong>. Inserisci i dati per l’intestazione delle fatture e l’invio tramite SDI o PEC.</p>
|
||||
|
||||
<% plan_return = params[:plan].presence_in(%w[premium_light premium_full]) %>
|
||||
<% unless @club.billing_profile_complete? %>
|
||||
<div class="flash alert" style="margin:16px 0">
|
||||
<strong>Obbligatori per abbonarti a un piano premium.</strong>
|
||||
<%= billing_profile_incomplete_message(@club) %>
|
||||
<% if plan_return %>
|
||||
Dopo il salvataggio potrai procedere al pagamento di <strong><%= Plan[plan_return].name %></strong>.
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="card">
|
||||
<%= form_with url: public_club_billing_profile_path(@club), method: :patch do %>
|
||||
<%= hidden_field_tag :plan, plan_return if plan_return %>
|
||||
<%= hidden_field_tag :interval, params[:interval] if params[:interval].present? %>
|
||||
<%= render "shared/billing_profile_fields", record: @club %>
|
||||
<%= submit_tag "Salva dati di fatturazione", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
|
||||
<div class="wrap" style="padding-top:20px">
|
||||
<h1>Abbonamento</h1>
|
||||
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription %>
|
||||
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription, on_billing_page: true %>
|
||||
|
||||
<%= render "shared/stripe_secure_payment" %>
|
||||
<%= render "shared/plan_cards", show_stripe_portal: true %>
|
||||
<%= render "shared/plan_cards", show_stripe_portal: false %>
|
||||
<%= render "shared/subscription_cancel", club: @club, subscription: @subscription %>
|
||||
|
||||
<%= render "shared/billing_documents", club: @club, payments: @payments, invoices: @invoices %>
|
||||
<%= render "shared/billing_documents", club: @club, payments: @payments %>
|
||||
|
||||
<p style="margin-top:24px"><%= link_to "← Società", public_club_path(@club) %></p>
|
||||
</div>
|
||||
|
||||
@@ -22,9 +22,16 @@
|
||||
<%= label_tag :plan, "Piano iniziale (per tutta la società)" %>
|
||||
<%= select_tag :plan, options_for_select([
|
||||
["Free — 1 responsabile trasmissione per squadra, 1 live", "free"],
|
||||
["Premium Light — €40/anno o €5/mese", "premium_light"],
|
||||
["Premium Full — €200/anno o €20/mese", "premium_full"]
|
||||
], params[:plan] || "free") %>
|
||||
["Premium Light", "premium_light"],
|
||||
["Premium Full", "premium_full"]
|
||||
], params[:plan] || "free"), id: "club_plan_select" %>
|
||||
<div id="club_plan_interval" style="margin-top:10px">
|
||||
<%= label_tag :interval, "Fatturazione premium" %>
|
||||
<%= select_tag :interval, options_for_select([
|
||||
["Annuale — €40/anno (Light) o €200/anno (Full)", "yearly"],
|
||||
["Mensile — €5/mese (Light) o €20/mese (Full)", "monthly"]
|
||||
], params[:interval] || "yearly") %>
|
||||
</div>
|
||||
<%= render "shared/stripe_secure_payment", compact: true %>
|
||||
<%= submit_tag "Crea società", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<h2>Diretta terminata</h2>
|
||||
<p>Lo streaming di questa partita è stato chiuso.</p>
|
||||
<% if @session.ended_at %>
|
||||
<p class="stream-ended-meta">Chiusa il <%= l(@session.ended_at.in_time_zone, format: :long) %></p>
|
||||
<p class="stream-ended-meta">Chiusa il <%= l_local(@session.ended_at) %></p>
|
||||
<% end %>
|
||||
<p class="stream-ended-hint">Il punteggio finale resta visibile sopra.</p>
|
||||
<%= link_to "Tutte le dirette", public_live_index_path, class: "btn btn-secondary stream-ended-btn" %>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<td><strong><%= match.opponent_name %></strong></td>
|
||||
<td>
|
||||
<% if match.scheduled_at %>
|
||||
<%= l match.scheduled_at.in_time_zone, format: :long %>
|
||||
<%= l_local(match.scheduled_at) %>
|
||||
<% else %>
|
||||
<span style="color:#888">Senza orario</span>
|
||||
<% end %>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<p>Registrazione indicizzata. Il playback dipende dalla retention del piano Premium.</p>
|
||||
<% end %>
|
||||
<% if @recording.expires_at %>
|
||||
<p class="stream-ended-meta" style="margin-top:12px">Disponibile fino al <%= l @recording.expires_at, format: :long %></p>
|
||||
<p class="stream-ended-meta" style="margin-top:12px">Disponibile fino al <%= l_local(@recording.expires_at) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<%= render "shared/input_toggle",
|
||||
name: :password,
|
||||
label: "Password",
|
||||
input_type: "text",
|
||||
required: true,
|
||||
autocomplete: "current-password" %>
|
||||
<p class="auth-forgot"><%= link_to "Password dimenticata?", public_password_forgot_path %></p>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<% elsif plan.slug == "free" %>
|
||||
<p style="color:#888;font-size:0.85rem">Contatta il supporto per downgrade.</p>
|
||||
<% elsif MatchLiveTv.stripe_enabled? %>
|
||||
<%= link_to "Attiva #{plan.name}", public_club_checkout_path(@team.club, plan: plan.slug), class: "btn btn-primary" %>
|
||||
<%= link_to "Gestisci abbonamento società", public_club_billing_path(@team.club), class: "btn btn-primary" %>
|
||||
<% else %>
|
||||
<p style="color:#888">Stripe non configurato</p>
|
||||
<% end %>
|
||||
@@ -41,9 +41,7 @@
|
||||
<p style="color:#aaa">Esigenze diverse? <%= mail_to "info@matchlive.it", "Contattaci" %> per un'offerta su misura.</p>
|
||||
</div>
|
||||
|
||||
<% if @entitlements.premium_active? && MatchLiveTv.stripe_enabled? %>
|
||||
<p style="margin-top:20px"><%= button_to "Gestisci su Stripe", public_club_portal_path(@team.club), class: "btn btn-secondary" %></p>
|
||||
<% end %>
|
||||
<p style="margin-top:20px"><%= link_to "Pagamenti e fatture della società", public_club_billing_path(@team.club) %></p>
|
||||
|
||||
<p><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<%# locals: (club:, payments:, invoices:) %>
|
||||
<%# locals: (club:, payments:) %>
|
||||
<section class="billing-documents" style="margin-top:40px">
|
||||
<h2>Pagamenti e fatture</h2>
|
||||
<p style="color:#888;font-size:0.9rem">
|
||||
Qui trovi lo storico dei pagamenti e le fatture emesse alla società.
|
||||
I pagamenti premium sono elaborati in modo sicuro tramite Stripe.
|
||||
Storico dei pagamenti della società e relative fatture emesse da Match Live TV.
|
||||
</p>
|
||||
|
||||
<% unless club.billing_profile_complete? %>
|
||||
<div class="flash alert" style="margin:16px 0">
|
||||
<strong>Dati di fatturazione incompleti.</strong>
|
||||
Compila ragione sociale, indirizzo, P.IVA/CF e SDI o PEC per ricevere le fatture.
|
||||
Compila ragione sociale, indirizzo, P.IVA/CF e SDI o PEC per ricevere le fatture via email.
|
||||
<%= link_to "Completa ora", public_club_billing_profile_path(club), class: "btn btn-secondary", style: "margin-top:10px;display:inline-block" %>
|
||||
</div>
|
||||
<% else %>
|
||||
@@ -19,14 +18,7 @@
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if MatchLiveTv.stripe_enabled? && club.subscription&.stripe_customer_id.present? %>
|
||||
<%= button_to "Sincronizza pagamenti da Stripe",
|
||||
public_club_billing_sync_payments_path(club),
|
||||
class: "btn btn-secondary",
|
||||
style: "margin:12px 0" %>
|
||||
<% end %>
|
||||
|
||||
<h3 style="margin-top:28px;font-size:1.1rem">Pagamenti effettuati</h3>
|
||||
<h3 style="margin-top:28px;font-size:1.1rem">Pagamenti e fatture</h3>
|
||||
<% if payments.any? %>
|
||||
<table class="data billing-table">
|
||||
<thead>
|
||||
@@ -34,54 +26,34 @@
|
||||
<th>Data</th>
|
||||
<th>Descrizione</th>
|
||||
<th>Importo</th>
|
||||
<th>Stato</th>
|
||||
<th>Stato pagamento</th>
|
||||
<th>Fattura</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% payments.each do |payment| %>
|
||||
<% inv = payment.invoice_for_display %>
|
||||
<tr>
|
||||
<td><%= payment.paid_at ? l(payment.paid_at, format: :short) : "—" %></td>
|
||||
<td><%= payment.description.presence || payment.plan_slug&.humanize || "Abbonamento" %></td>
|
||||
<td><%= payment.paid_at ? l_local(payment.paid_at, format: :short) : "—" %></td>
|
||||
<td><%= payment.display_description %></td>
|
||||
<td><%= payment.formatted_amount %></td>
|
||||
<td><%= payment.status %></td>
|
||||
<td><%= payment.display_status %></td>
|
||||
<td>
|
||||
<% if payment.receipt_url.present? %>
|
||||
<%= link_to "Ricevuta Stripe", payment.receipt_url, target: "_blank", rel: "noopener" %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p style="color:#888">Nessun pagamento registrato. Dopo un abbonamento premium comparirà qui (anche tramite «Sincronizza pagamenti»).</p>
|
||||
<% end %>
|
||||
|
||||
<h3 style="margin-top:28px;font-size:1.1rem">Fatture</h3>
|
||||
<% if invoices.any? %>
|
||||
<table class="data billing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Numero</th>
|
||||
<th>Data</th>
|
||||
<th>Importo</th>
|
||||
<th>Stato</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% invoices.each do |invoice| %>
|
||||
<tr>
|
||||
<td><%= invoice.number %></td>
|
||||
<td><%= l(invoice.issued_on) %></td>
|
||||
<td><%= invoice.formatted_amount %></td>
|
||||
<td><%= invoice.status %></td>
|
||||
<td>
|
||||
<% if invoice.downloadable? %>
|
||||
<%= link_to "Scarica PDF", public_club_billing_invoice_path(club, invoice) %>
|
||||
<% if inv.present? %>
|
||||
<%= inv.display_number %>
|
||||
· <%= inv.display_status %>
|
||||
<% else %>
|
||||
—
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if inv&.downloadable? %>
|
||||
<%= link_to "Scarica PDF", public_club_billing_invoice_path(club, inv) %>
|
||||
<% elsif inv&.draft? %>
|
||||
<span style="color:#888">PDF in preparazione</span>
|
||||
<% else %>
|
||||
<span style="color:#888">—</span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -89,6 +61,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p style="color:#888">Nessuna fattura disponibile. Compariranno qui dopo l’emissione.</p>
|
||||
<p style="color:#888">Nessun pagamento registrato. Dopo un abbonamento premium comparirà qui.</p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
@@ -2,64 +2,65 @@
|
||||
<% if show_legend %>
|
||||
<h2 class="form-section-title" style="margin-top:24px">Dati di fatturazione</h2>
|
||||
<p style="color:#888;font-size:0.88rem;margin:-8px 0 16px">
|
||||
Obbligatori per ricevere le fatture elettroniche (P.IVA/CF, indirizzo, SDI o PEC).
|
||||
Tutti i campi contrassegnati sono obbligatori per abbonarti a un piano premium e per emettere le fatture.
|
||||
Per le società serve la P.IVA; per le persone fisiche il Codice Fiscale. Serve SDI <em>oppure</em> PEC.
|
||||
I pagamenti restano gestiti in modo sicuro da Stripe.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= label_tag "club[billing_entity_type]", "Tipo intestatario" %>
|
||||
<%= label_tag "club[billing_entity_type]", "Tipo intestatario *" %>
|
||||
<%= select_tag "club[billing_entity_type]",
|
||||
options_for_select(Club::BILLING_ENTITY_TYPES.map { |k, v| [v, k] }, record.billing_entity_type),
|
||||
include_blank: false %>
|
||||
|
||||
<%= label_tag "club[billing_legal_name]", "Ragione sociale / nome e cognome" %>
|
||||
<%= label_tag "club[billing_legal_name]", "Ragione sociale / nome e cognome *" %>
|
||||
<%= text_field_tag "club[billing_legal_name]", record.billing_legal_name, placeholder: "es. ASD Tigers Volley" %>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
||||
<div>
|
||||
<%= label_tag "club[billing_vat_number]", "Partita IVA" %>
|
||||
<%= label_tag "club[billing_vat_number]", "Partita IVA * (società)" %>
|
||||
<%= text_field_tag "club[billing_vat_number]", record.billing_vat_number, placeholder: "12345678901" %>
|
||||
</div>
|
||||
<div>
|
||||
<%= label_tag "club[billing_fiscal_code]", "Codice Fiscale" %>
|
||||
<%= label_tag "club[billing_fiscal_code]", "Codice Fiscale * (persona fisica)" %>
|
||||
<%= text_field_tag "club[billing_fiscal_code]", record.billing_fiscal_code, placeholder: "RSSMRA80A01H501U" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= label_tag "club[billing_email]", "Email fatturazione" %>
|
||||
<%= label_tag "club[billing_email]", "Email fatturazione *" %>
|
||||
<%= email_field_tag "club[billing_email]", record.billing_email, placeholder: "amministrazione@societa.it" %>
|
||||
|
||||
<%= label_tag "club[billing_phone]", "Telefono (opzionale)" %>
|
||||
<%= text_field_tag "club[billing_phone]", record.billing_phone %>
|
||||
|
||||
<%= label_tag "club[billing_address_line]", "Indirizzo" %>
|
||||
<%= label_tag "club[billing_address_line]", "Indirizzo *" %>
|
||||
<%= text_field_tag "club[billing_address_line]", record.billing_address_line, placeholder: "Via Roma 1" %>
|
||||
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:12px">
|
||||
<div>
|
||||
<%= label_tag "club[billing_city]", "Città" %>
|
||||
<%= label_tag "club[billing_city]", "Città *" %>
|
||||
<%= text_field_tag "club[billing_city]", record.billing_city %>
|
||||
</div>
|
||||
<div>
|
||||
<%= label_tag "club[billing_province]", "Prov." %>
|
||||
<%= label_tag "club[billing_province]", "Prov. *" %>
|
||||
<%= text_field_tag "club[billing_province]", record.billing_province, maxlength: 2, placeholder: "MI" %>
|
||||
</div>
|
||||
<div>
|
||||
<%= label_tag "club[billing_postal_code]", "CAP" %>
|
||||
<%= label_tag "club[billing_postal_code]", "CAP *" %>
|
||||
<%= text_field_tag "club[billing_postal_code]", record.billing_postal_code %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= label_tag "club[billing_country]", "Paese (ISO)" %>
|
||||
<%= label_tag "club[billing_country]", "Paese (ISO) *" %>
|
||||
<%= text_field_tag "club[billing_country]", record.billing_country.presence || "IT", maxlength: 2 %>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
||||
<div>
|
||||
<%= label_tag "club[billing_recipient_code]", "Codice destinatario SDI (7 caratteri)" %>
|
||||
<%= label_tag "club[billing_recipient_code]", "Codice destinatario SDI (7 car.) *" %>
|
||||
<%= text_field_tag "club[billing_recipient_code]", record.billing_recipient_code, maxlength: 7, placeholder: "XXXXXXX" %>
|
||||
</div>
|
||||
<div>
|
||||
<%= label_tag "club[billing_pec]", "PEC (alternativa a SDI)" %>
|
||||
<%= label_tag "club[billing_pec]", "PEC (alternativa a SDI) *" %>
|
||||
<%= email_field_tag "club[billing_pec]", record.billing_pec, placeholder: "pec@societa.it" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
<%# locals: (club:, entitlements: nil, subscription: nil) %>
|
||||
<%# locals: (club:, entitlements: nil, subscription: nil, on_billing_page: false) %>
|
||||
<% current_plan = entitlements&.plan || subscription&.plan || Plan["free"] %>
|
||||
<p style="color:#aaa;margin-bottom:8px">
|
||||
Società: <strong><%= club.name %></strong>
|
||||
· Piano attuale: <strong><%= current_plan.name %></strong>
|
||||
(valido per tutte le squadre)
|
||||
· <%= link_to "Gestisci abbonamento", public_club_billing_path(club) %>
|
||||
<% unless local_assigns[:on_billing_page] %>
|
||||
· <%= link_to "Gestisci abbonamento", public_club_billing_path(club) %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% if subscription&.stripe_subscription_id.present? %>
|
||||
<p style="color:#888;font-size:0.9rem;margin-top:0">
|
||||
Stato Stripe: <strong><%= subscription.status %></strong>
|
||||
<% if subscription.current_period_end.present? %>
|
||||
· Rinnovo: <strong><%= l(subscription.current_period_end, format: :long) %></strong>
|
||||
<% end %>
|
||||
<% if subscription.cancel_at_period_end? %>
|
||||
· <span style="color:#e53935">Disdetta alla scadenza</span>
|
||||
<span style="color:#e53935">
|
||||
Abbonamento valido fino al
|
||||
<strong><%= subscription.current_period_end.present? ? l_local(subscription.current_period_end.to_date) : "fine periodo" %></strong>,
|
||||
poi piano Free.
|
||||
</span>
|
||||
<% elsif subscription.current_period_end.present? %>
|
||||
Prossimo rinnovo: <strong><%= l_local(subscription.current_period_end) %></strong>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
<%# locals: (name:, label:, value: nil, input_type: "text", required: false, autocomplete: nil, minlength: nil, id: nil) %>
|
||||
<%# locals: (name:, label:, value: nil, input_type: "text", required: false, autocomplete: nil, minlength: nil, id: nil, masked: nil) %>
|
||||
<% field_id = local_assigns.fetch(:id, nil).presence || name.to_s %>
|
||||
<% visible_type = local_assigns.fetch(:input_type, "text").to_s == "email" ? "email" : "text" %>
|
||||
<div class="input-toggle" data-input-toggle>
|
||||
<% visible_type = local_assigns.fetch(:input_type, "text").to_s %>
|
||||
<% is_email = visible_type == "email" %>
|
||||
<% masked = local_assigns.key?(:masked) ? local_assigns[:masked] : !is_email %>
|
||||
|
||||
<div class="input-toggle<%= " input-toggle--plain" unless masked %>"<%= " data-input-toggle" if masked %>>
|
||||
<%= label_tag field_id, label %>
|
||||
<div class="input-toggle__field">
|
||||
<%= tag.input(
|
||||
type: "password",
|
||||
name: name,
|
||||
id: field_id,
|
||||
value: local_assigns.fetch(:value, nil),
|
||||
required: local_assigns.fetch(:required, false),
|
||||
autocomplete: local_assigns.fetch(:autocomplete, nil),
|
||||
minlength: local_assigns.fetch(:minlength, nil),
|
||||
class: "input-toggle__input",
|
||||
data: { visible_type: visible_type }
|
||||
) %>
|
||||
<button type="button" class="input-toggle__btn" aria-label="Mostra <%= label.downcase %>" data-label-hide="Nascondi <%= label.downcase %>">
|
||||
<i class="fa-regular fa-eye" aria-hidden="true"></i>
|
||||
</button>
|
||||
<% if masked %>
|
||||
<%= tag.input(
|
||||
type: "password",
|
||||
name: name,
|
||||
id: field_id,
|
||||
value: local_assigns.fetch(:value, nil),
|
||||
required: local_assigns.fetch(:required, false),
|
||||
autocomplete: local_assigns.fetch(:autocomplete, nil),
|
||||
minlength: local_assigns.fetch(:minlength, nil),
|
||||
class: "input-toggle__input",
|
||||
data: { visible_type: "text" }
|
||||
) %>
|
||||
<button type="button" class="input-toggle__btn" aria-label="Mostra <%= label.downcase %>" data-label-hide="Nascondi <%= label.downcase %>">
|
||||
<i class="fa-regular fa-eye" aria-hidden="true"></i>
|
||||
</button>
|
||||
<% elsif is_email %>
|
||||
<%= email_field_tag name, local_assigns.fetch(:value, nil),
|
||||
id: field_id,
|
||||
required: local_assigns.fetch(:required, false),
|
||||
autocomplete: local_assigns.fetch(:autocomplete, nil),
|
||||
class: "input-toggle__input input-toggle__input--plain" %>
|
||||
<% else %>
|
||||
<%= text_field_tag name, local_assigns.fetch(:value, nil),
|
||||
id: field_id,
|
||||
required: local_assigns.fetch(:required, false),
|
||||
autocomplete: local_assigns.fetch(:autocomplete, nil),
|
||||
minlength: local_assigns.fetch(:minlength, nil),
|
||||
class: "input-toggle__input input-toggle__input--plain" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<% entitlements ||= @entitlements %>
|
||||
<% subscription ||= @subscription %>
|
||||
<% current_slug = local_assigns.fetch(:current_plan_slug, nil).presence || entitlements&.plan&.slug || subscription&.plan&.slug || "free" %>
|
||||
<% current_interval = subscription&.billing_interval %>
|
||||
<% billing_mode = club.present? %>
|
||||
|
||||
<div class="plans">
|
||||
@@ -10,18 +11,23 @@
|
||||
<% action = billing_mode ? plan_billing_action(
|
||||
current_slug: current_slug,
|
||||
target_plan: plan,
|
||||
stripe_subscription_active: stripe_subscription_active?(subscription)
|
||||
stripe_subscription_active: stripe_subscription_active?(subscription),
|
||||
current_interval: current_interval
|
||||
) : nil %>
|
||||
<div class="plan-card <%= "featured" if plan.slug == current_slug || (!billing_mode && plan.slug == "premium_light") %>">
|
||||
<h3><%= plan.name %></h3>
|
||||
<% if plan.slug == "free" %>
|
||||
<div class="plan-price">€0</div>
|
||||
<% elsif plan.slug == "premium_light" %>
|
||||
<div class="plan-price" style="color:#e53935">€40<span style="font-size:1rem;font-weight:400">/anno</span></div>
|
||||
<p style="color:#aaa;font-size:0.9rem;margin:-8px 0 12px">oppure <strong>€5</strong>/mese</p>
|
||||
<% light_yearly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %>
|
||||
<% light_monthly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %>
|
||||
<div class="plan-price" style="color:#e53935"><%= light_yearly %></div>
|
||||
<p class="plan-price-alt">oppure <strong><%= light_monthly %></strong></p>
|
||||
<% else %>
|
||||
<div class="plan-price">€200<span style="font-size:1rem;font-weight:400">/anno</span></div>
|
||||
<p style="color:#aaa;font-size:0.9rem;margin:-8px 0 12px">oppure <strong>€20</strong>/mese</p>
|
||||
<% full_yearly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %>
|
||||
<% full_monthly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %>
|
||||
<div class="plan-price"><%= full_yearly %></div>
|
||||
<p class="plan-price-alt">oppure <strong><%= full_monthly %></strong></p>
|
||||
<% end %>
|
||||
<ul>
|
||||
<li>Responsabili trasmissione: <strong><%= plan.max_staff_transmission || "illimitato" %></strong> / anno per squadra</li>
|
||||
@@ -30,26 +36,45 @@
|
||||
<li>YouTube: <% if plan.slug == "premium_light" %>Match TV Light<% elsif plan.youtube_enabled? %>canale società<% else %>no<% end %></li>
|
||||
</ul>
|
||||
<% if billing_mode %>
|
||||
<% case action[:kind] %>
|
||||
<% when :current %>
|
||||
<% action_kind = action[:kind] %>
|
||||
<% if action_kind == :current %>
|
||||
<span class="btn btn-secondary" style="opacity:0.7"><%= action[:label] %></span>
|
||||
<% when :contact, :disabled %>
|
||||
<p style="color:#888;font-size:0.85rem"><%= action[:label] %></p>
|
||||
<% when :checkout, :change %>
|
||||
<%= link_to action[:label], public_club_checkout_path(club, plan: plan.slug), class: "btn btn-primary" %>
|
||||
<% elsif action_kind == :contact || action_kind == :disabled %>
|
||||
<p class="plan-action-hint"><%= action[:label] %></p>
|
||||
<% elsif action_kind == :checkout_options || action_kind == :change_options || action_kind == :interval_switch %>
|
||||
<% if billing_profile_blocks_premium?(club) %>
|
||||
<p class="plan-action-hint" style="color:#e53935;margin-bottom:10px">
|
||||
<%= billing_profile_incomplete_message(club) %>
|
||||
</p>
|
||||
<%= link_to "Completa dati di fatturazione",
|
||||
public_club_billing_profile_path(club, plan: plan.slug),
|
||||
class: "btn btn-primary" %>
|
||||
<% else %>
|
||||
<div class="plan-interval-actions">
|
||||
<% plan_intervals_for_display(action[:intervals]).each do |interval| %>
|
||||
<% btn_kind = action_kind == :checkout_options ? :checkout : :change %>
|
||||
<%= link_to plan_interval_button_label(plan, interval, kind: btn_kind),
|
||||
plan_interval_checkout_path(club, plan, interval),
|
||||
class: plan_interval_button_class(interval) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if action_kind == :interval_switch && action[:current_interval].present? %>
|
||||
<p class="plan-action-hint">Fatturazione attuale: <%= Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: action[:current_interval]) %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% elsif plan.slug == "free" %>
|
||||
<%= link_to "Inizia gratis", public_signup_path, class: "btn btn-secondary" %>
|
||||
<% else %>
|
||||
<%= link_to "Registrati", public_signup_path(plan: plan.slug), class: "btn btn-primary" %>
|
||||
<div class="plan-interval-actions">
|
||||
<% plan_intervals_for_display(Billing::Stripe::PriceCatalog.available_intervals(plan_slug: plan.slug)).each do |interval| %>
|
||||
<%= link_to "Registrati — #{Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: interval)}",
|
||||
public_signup_path(plan: plan.slug, interval: interval),
|
||||
class: plan_interval_button_class(interval) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if show_stripe_portal && billing_mode && subscription&.premium? && MatchLiveTv.stripe_enabled? && subscription&.stripe_customer_id.present? %>
|
||||
<p style="margin-top:20px">
|
||||
<%= button_to "Gestisci fatturazione su Stripe", public_club_portal_path(club), class: "btn btn-secondary" %>
|
||||
<span style="color:#888;font-size:0.85rem;display:block;margin-top:8px">Cambia metodo di pagamento, scarica fatture o disdici l'abbonamento.</span>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
36
backend/app/views/shared/_subscription_cancel.html.erb
Normal file
36
backend/app/views/shared/_subscription_cancel.html.erb
Normal file
@@ -0,0 +1,36 @@
|
||||
<%# locals: (club:, subscription:) %>
|
||||
<% return unless subscription&.premium? && subscription.stripe_subscription_id.present? && MatchLiveTv.stripe_enabled? %>
|
||||
|
||||
<section class="subscription-cancel" style="margin-top:24px;padding:16px;border:1px solid #333;border-radius:8px">
|
||||
<% if subscription.cancel_at_period_end? %>
|
||||
<p style="color:#e53935;margin:0 0 8px">
|
||||
<strong>Disdetta programmata.</strong>
|
||||
Il piano <strong><%= subscription.plan.name %></strong> resta attivo fino al
|
||||
<% if subscription.current_period_end.present? %>
|
||||
<strong><%= l_local(subscription.current_period_end.to_date) %></strong>.
|
||||
<% else %>
|
||||
<strong>fine del periodo già pagato</strong>.
|
||||
<% end %>
|
||||
Da quel giorno la società passerà automaticamente al piano <strong>Free</strong>.
|
||||
</p>
|
||||
<p style="color:#888;font-size:0.88rem;margin:0">
|
||||
Per ripristinare l'abbonamento prima della scadenza, contatta il supporto Match Live TV.
|
||||
</p>
|
||||
<% else %>
|
||||
<p style="color:#aaa;margin:0 0 12px;font-size:0.9rem">
|
||||
Puoi disdire in qualsiasi momento: l'abbonamento resta valido fino alla fine del periodo già pagato,
|
||||
poi passi al piano Free senza ulteriori addebiti.
|
||||
</p>
|
||||
<%= button_to "Annulla abbonamento",
|
||||
public_club_billing_cancel_subscription_path(club),
|
||||
method: :post,
|
||||
class: "btn btn-outline",
|
||||
form: {
|
||||
data: {
|
||||
turbo_confirm: subscription.current_period_end.present? ?
|
||||
"Confermi la disdetta? Il piano #{subscription.plan.name} resterà attivo fino al #{l_local(subscription.current_period_end)}, poi passerai al Free." :
|
||||
"Confermi la disdetta dell'abbonamento?"
|
||||
}
|
||||
} %>
|
||||
<% end %>
|
||||
</section>
|
||||
Reference in New Issue
Block a user