Compare commits

...
Author SHA1 Message Date
eminuxandCursor a51d5e8da5 Non inviare in automatico le istruzioni di bonifico.
IBAN e causale restano da comunicare a mano dall'admin; la richiesta registra solo l'ordine in attesa.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 18:40:45 +02:00
eminuxandCursor 5bcb4170c7 Mostra il piano attivo dopo il bonifico e allinea il flusso società.
Dopo la conferma admin la card restava su «Paga con bonifico»; la società parte da Free, l'owner è staff trasmissione e le mail non bloccano se manca SMTP.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 18:31:39 +02:00
31 changed files with 358 additions and 112 deletions
@@ -23,23 +23,28 @@ module Public
ClubMembership.create!(user: current_user, club: club, role: "owner") ClubMembership.create!(user: current_user, club: club, role: "owner")
first_team_name = params.dig(:first_team, :name).presence || t("club.new.default_first_team_name") first_team_name = params.dig(:first_team, :name).presence || t("club.new.default_first_team_name")
club.teams.create!( first_team = club.teams.create!(
name: first_team_name, name: first_team_name,
sport: club.sport sport: club.sport
) )
plan = params[:plan].presence_in(%w[free premium_light premium_full]) || "free" desired_plan = params[:plan].presence_in(%w[free premium_light premium_full]) || "free"
Billing::AssignPlan.call(club: club, plan_slug: plan) Billing::AssignPlan.call(club: club, plan_slug: "free")
Teams::StaffAssignment.designate_club_owner!(team: first_team, user: current_user)
if plan.in?(%w[premium_light premium_full]) && MatchLiveTv.stripe_enabled? if desired_plan.in?(%w[premium_light premium_full])
unless club.billing_profile_complete? interval = Billing::Stripe::PriceCatalog::DEFAULT_INTERVAL
redirect_to public_club_billing_profile_path(club, plan: plan, interval: checkout_interval_param), if MatchLiveTv.stripe_enabled?
alert: t("flash.clubs.complete_billing_first") unless club.billing_profile_complete?
return redirect_to public_club_billing_profile_path(club, plan: desired_plan, interval: interval),
alert: t("flash.clubs.complete_billing_first")
return
end
redirect_to public_club_checkout_path(club, plan: desired_plan, interval: interval)
else
redirect_to public_club_billing_path(club), notice: t("flash.clubs.created_complete_subscription")
end end
interval = checkout_interval_param
redirect_to public_club_checkout_path(club, plan: plan, interval: interval)
else else
redirect_to public_club_path(club), notice: t("flash.clubs.created") redirect_to public_club_path(club), notice: t("flash.clubs.created")
end end
@@ -19,6 +19,7 @@ module Public
require_club_owner!(@club) require_club_owner!(@club)
team = @club.teams.create!(team_params) team = @club.teams.create!(team_params)
attach_branding_logo(team) attach_branding_logo(team)
Teams::StaffAssignment.designate_club_owner!(team: team, user: current_user)
redirect_to public_club_path(@club), notice: t("flash.teams.added", name: team.name) redirect_to public_club_path(@club), notice: t("flash.teams.added", name: team.name)
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
flash.now[:alert] = e.record.errors.full_messages.join(", ") flash.now[:alert] = e.record.errors.full_messages.join(", ")
@@ -11,6 +11,13 @@ module Public
quote = club&.active_billing_quote quote = club&.active_billing_quote
if quote if quote
if quote.plan_slug == target_plan.slug if quote.plan_slug == target_plan.slug
if current_paid_plan?(subscription, target_plan)
return {
kind: :current,
label: "#{I18n.t('billing.actions.current_plan')}#{quote.price_label}"
}
end
return { kind: :quoted, plan: target_plan, quote: quote, intervals: [quote.billing_interval] } return { kind: :quoted, plan: target_plan, quote: quote, intervals: [quote.billing_interval] }
end end
@@ -19,6 +26,9 @@ module Public
intervals = bank_transfer_intervals_for(target_plan) intervals = bank_transfer_intervals_for(target_plan)
unless MatchLiveTv.stripe_enabled? unless MatchLiveTv.stripe_enabled?
if current_paid_plan?(subscription, target_plan)
return { kind: :current, label: current_plan_label(target_plan, subscription) }
end
if MatchLiveTv.bank_transfer_configured? if MatchLiveTv.bank_transfer_configured?
return { kind: :bank_only, plan: target_plan, intervals: intervals } return { kind: :bank_only, plan: target_plan, intervals: intervals }
end end
@@ -28,6 +38,9 @@ module Public
stripe_intervals = Billing::Stripe::PriceCatalog.available_intervals(plan_slug: target_plan.slug) stripe_intervals = Billing::Stripe::PriceCatalog.available_intervals(plan_slug: target_plan.slug)
if stripe_intervals.empty? if stripe_intervals.empty?
if current_paid_plan?(subscription, target_plan)
return { kind: :current, label: current_plan_label(target_plan, subscription) }
end
if MatchLiveTv.bank_transfer_configured? if MatchLiveTv.bank_transfer_configured?
return { kind: :bank_only, plan: target_plan, intervals: intervals } return { kind: :bank_only, plan: target_plan, intervals: intervals }
end end
@@ -54,6 +67,10 @@ module Public
} }
end end
if current_paid_plan?(subscription, target_plan)
return { kind: :current, label: current_plan_label(target_plan, subscription) }
end
if current_slug == "free" || !stripe_subscription_active if current_slug == "free" || !stripe_subscription_active
{ kind: :checkout_options, plan: target_plan, intervals: stripe_intervals.presence || intervals, subscription: subscription } { kind: :checkout_options, plan: target_plan, intervals: stripe_intervals.presence || intervals, subscription: subscription }
else else
@@ -136,8 +153,21 @@ module Public
return false unless MatchLiveTv.bank_transfer_configured? return false unless MatchLiveTv.bank_transfer_configured?
return false if quote && !quote.matches?(plan.slug, interval) return false if quote && !quote.matches?(plan.slug, interval)
return false if pending_transfer&.awaiting_payment? return false if pending_transfer&.awaiting_payment?
return false if current_paid_plan?(club.subscription, plan)
true true
end end
def current_paid_plan?(subscription, target_plan)
return false unless subscription&.active? && subscription.premium?
return false if subscription.plan_change_pending?
subscription.plan.slug == target_plan.slug
end
def current_plan_label(target_plan, subscription)
interval = subscription&.billing_interval.presence || Billing::Stripe::PriceCatalog::DEFAULT_INTERVAL
"#{I18n.t('billing.actions.current_plan')}#{Billing::Stripe::PriceCatalog.label(plan_slug: target_plan.slug, interval: interval)}"
end
end end
end end
@@ -88,7 +88,7 @@ module Billing
mailer_action: :plan_activated_with_invoice mailer_action: :plan_activated_with_invoice
) )
else else
BankTransferMailer.with(order: @order.reload).plan_activated.deliver_now MatchLiveTv.deliver_mail(BankTransferMailer.with(order: @order.reload).plan_activated)
end end
end end
@@ -26,8 +26,10 @@ module Billing
@invoice.update!(status: "issued") @invoice.update!(status: "issued")
Billing::InvoiceMailer.with(invoice: @invoice).public_send(@mailer_action).deliver_now mail = Billing::InvoiceMailer.with(invoice: @invoice).public_send(@mailer_action)
@invoice.update!(status: "sent", emailed_at: Time.current) if MatchLiveTv.deliver_mail(mail)
@invoice.update!(status: "sent", emailed_at: Time.current)
end
@invoice @invoice
end end
@@ -28,7 +28,6 @@ module Billing
existing = @club.billing_transfer_orders.awaiting_payment.first existing = @club.billing_transfer_orders.awaiting_payment.first
if existing if existing
if existing.plan_slug == @plan_slug && existing.billing_interval == @interval && existing.amount_cents == amount_cents if existing.plan_slug == @plan_slug && existing.billing_interval == @interval && existing.amount_cents == amount_cents
BankTransferMailer.with(order: existing).instructions.deliver_now
return existing return existing
end end
@@ -59,7 +58,8 @@ module Billing
) )
end end
BankTransferMailer.with(order: order).instructions.deliver_now # Le istruzioni (IBAN/causale) le manda a mano l'admin da /admin/billing.
Rails.logger.info("[BankTransfer] ordine #{order.reference_code} in attesa, nessuna mail istruzioni")
order order
end end
@@ -23,5 +23,12 @@ module Teams
ut.update!(staff_kind: "transmission") ut.update!(staff_kind: "transmission")
ut ut
end end
def self.designate_club_owner!(team:, user:)
membership = user.user_teams.find_or_initialize_by(team: team)
membership.role = "member" if membership.new_record?
membership.save!
call(team: team, user: user, membership: membership)
end
end end
end end
@@ -42,6 +42,10 @@
</header> </header>
<div class="billing-pending-card__body"> <div class="billing-pending-card__body">
<p style="margin:0 0 12px"> <p style="margin:0 0 12px">
<%= t("admin.billing.index.transfers_table.holder") %>:
<strong><%= MatchLiveTv.bank_transfer_account_holder %></strong><br>
<%= t("admin.billing.index.transfers_table.iban") %>:
<strong><%= MatchLiveTv.bank_transfer_iban %></strong><br>
<%= t("admin.billing.index.transfers_table.causal") %>: <%= t("admin.billing.index.transfers_table.causal") %>:
<strong><%= order.payment_causal %></strong> <strong><%= order.payment_causal %></strong>
</p> </p>
+1 -1
View File
@@ -13,7 +13,7 @@
<script src="/admin-dashboard.js?v=1" defer></script> <script src="/admin-dashboard.js?v=1" defer></script>
<% end %> <% end %>
<link rel="stylesheet" href="/confirm-forms.css?v=4"> <link rel="stylesheet" href="/confirm-forms.css?v=4">
<script src="/confirm-forms.js?v=6" defer></script> <script src="/confirm-forms.js?v=7" defer></script>
</head> </head>
<body class="<%= content_for?(:body_class) ? yield(:body_class) : 'admin-body' %>"> <body class="<%= content_for?(:body_class) ? yield(:body_class) : 'admin-body' %>">
<header class="admin-header"> <header class="admin-header">
+1 -1
View File
@@ -23,7 +23,7 @@
<script src="/roster-form.js?v=1" defer></script> <script src="/roster-form.js?v=1" defer></script>
<script src="/password-toggle.js?v=2" defer></script> <script src="/password-toggle.js?v=2" defer></script>
<link rel="stylesheet" href="/confirm-forms.css?v=4"> <link rel="stylesheet" href="/confirm-forms.css?v=4">
<script src="/confirm-forms.js?v=6" defer></script> <script src="/confirm-forms.js?v=7" defer></script>
<script src="/cookie-consent.js?v=1" defer></script> <script src="/cookie-consent.js?v=1" defer></script>
</body> </body>
</html> </html>
@@ -18,7 +18,7 @@
</main> </main>
<%= render "shared/marketing_footer" %> <%= render "shared/marketing_footer" %>
<link rel="stylesheet" href="/confirm-forms.css?v=4"> <link rel="stylesheet" href="/confirm-forms.css?v=4">
<script src="/confirm-forms.js?v=6" defer></script> <script src="/confirm-forms.js?v=7" defer></script>
<script src="/cookie-consent.js?v=1" defer></script> <script src="/cookie-consent.js?v=1" defer></script>
</body> </body>
</html> </html>
@@ -18,7 +18,7 @@
</div> </div>
<% else %> <% else %>
<% if @quote %> <% if @quote %>
<%= render "shared/quoted_price_banner", quote: @quote %> <%= render "shared/quoted_price_banner", quote: @quote, subscription: @subscription %>
<% elsif MatchLiveTv.stripe_enabled? %> <% elsif MatchLiveTv.stripe_enabled? %>
<%= render "shared/stripe_secure_payment" %> <%= render "shared/stripe_secure_payment" %>
<% end %> <% end %>
+2 -9
View File
@@ -24,15 +24,8 @@
[t("club.new.plan_free"), "free"], [t("club.new.plan_free"), "free"],
[t("club.new.plan_light"), "premium_light"], [t("club.new.plan_light"), "premium_light"],
[t("club.new.plan_full"), "premium_full"] [t("club.new.plan_full"), "premium_full"]
], params[:plan] || "free"), id: "club_plan_select" %> ], params[:plan] || "free") %>
<div id="club_plan_interval" style="margin-top:10px"> <p class="muted" style="margin:8px 0 16px"><%= t("club.new.plan_hint") %></p>
<%= label_tag :interval, t("club.new.interval_label") %>
<%= select_tag :interval, options_for_select([
[t("club.new.interval_yearly"), "yearly"],
[t("club.new.interval_monthly"), "monthly"]
], params[:interval] || "yearly") %>
</div>
<%= render "shared/stripe_secure_payment", compact: true %>
<%= submit_tag t("club.new.submit"), class: "btn btn-primary" %> <%= submit_tag t("club.new.submit"), class: "btn btn-primary" %>
<% end %> <% end %>
</div> </div>
@@ -1,33 +1,33 @@
<% team = local_assigns[:team] %> <% team = local_assigns[:team] %>
<% club = local_assigns[:club] %>
<% can_manage = local_assigns[:can_manage] %> <% can_manage = local_assigns[:can_manage] %>
<% owner_membership = local_assigns[:owner_membership] %>
<% staff_memberships = local_assigns[:staff_memberships] %> <% staff_memberships = local_assigns[:staff_memberships] %>
<% pending_invitations = local_assigns[:pending_invitations] %> <% pending_invitations = local_assigns[:pending_invitations] %>
<% recordings = local_assigns[:recordings] %> <% recordings = local_assigns[:recordings] %>
<% entitlements = local_assigns[:entitlements] %> <% entitlements = local_assigns[:entitlements] %>
<% club_owner = club.owner %>
<% extra_staff = staff_memberships.reject { |ut| ut.user_id == club_owner&.id } %>
<section class="team-streaming-staff" id="responsabili-trasmissione"> <section class="team-streaming-staff" id="responsabili-trasmissione">
<% if can_manage %> <% if club_owner %>
<% if owner_membership&.staff_kind.blank? %> <div class="card team-streaming-staff__self">
<div class="card team-streaming-staff__self"> <h2><%= t("team.streaming_staff.principal_heading") %></h2>
<h2><%= t("team.streaming_staff.self_account_heading", email: current_user.email) %></h2> <p>
<p class="muted"> <%= raw t("team.streaming_staff.principal_body_html", email: club_owner.email) %>
<%= raw t("team.streaming_staff.self_account_hint") %> </p>
</p> <p class="muted">
<div class="team-details-actions"> <%= raw t("team.streaming_staff.principal_score_hint_html") %>
<%= button_to t("team.streaming_staff.self_assign_submit"), public_team_assign_self_staff_path(team), method: :post, params: { staff_kind: "transmission" }, class: "btn btn-primary" %> </p>
</div> </div>
</div>
<% end %>
<% end %> <% end %>
<h2><%= t("team.streaming_staff.staff_heading") %></h2> <% if extra_staff.any? %>
<div class="card"> <h2><%= t("team.streaming_staff.extra_staff_heading") %></h2>
<% if staff_memberships.any? %> <div class="card">
<table class="data"> <table class="data">
<thead><tr><th><%= t("team.streaming_staff.col_name") %></th><th><%= t("team.streaming_staff.col_email") %></th><th><%= t("team.streaming_staff.col_role") %></th><% if can_manage %><th></th><% end %></tr></thead> <thead><tr><th><%= t("team.streaming_staff.col_name") %></th><th><%= t("team.streaming_staff.col_email") %></th><th><%= t("team.streaming_staff.col_role") %></th><% if can_manage %><th></th><% end %></tr></thead>
<tbody> <tbody>
<% staff_memberships.each do |ut| %> <% extra_staff.each do |ut| %>
<tr> <tr>
<td> <td>
<%= ut.user.name %> <%= ut.user.name %>
@@ -37,9 +37,7 @@
<td><%= t("team.streaming_staff.role_transmission") %></td> <td><%= t("team.streaming_staff.role_transmission") %></td>
<% if can_manage %> <% if can_manage %>
<td> <td>
<% if ut.user_id == current_user.id && team.club.owned_by?(current_user) %> <% if ut.role == "member" %>
<%= button_to t("team.streaming_staff.clear_self_staff"), public_team_clear_self_staff_path(team), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: t("team.streaming_staff.clear_self_staff_confirm"), confirm_kind: "delete" } } %>
<% elsif ut.role == "member" %>
<%= button_to t("team.streaming_staff.revoke"), public_team_remove_member_path(team, ut.user), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: t("team.streaming_staff.revoke_confirm", name: ut.user.name), confirm_kind: "delete" } } %> <%= button_to t("team.streaming_staff.revoke"), public_team_remove_member_path(team, ut.user), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: t("team.streaming_staff.revoke_confirm", name: ut.user.name), confirm_kind: "delete" } } %>
<% end %> <% end %>
</td> </td>
@@ -48,13 +46,8 @@
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<% else %> </div>
<p class="muted"> <% end %>
<%= t("team.streaming_staff.no_staff") %>
<% if can_manage %><%= link_to t("team.streaming_staff.invite_someone"), "#invita-trasmissione" %>.<% end %>
</p>
<% end %>
</div>
<% if can_manage %> <% if can_manage %>
<div class="card team-invite-form" id="invita-trasmissione"> <div class="card team-invite-form" id="invita-trasmissione">
@@ -1,15 +1,10 @@
<%# locals: (order:) %> <%# locals: (order:) %>
<% return if order.blank? %> <% return if order.blank? %>
<div class="card" style="margin-top:16px;border-color:#3d3520;background:#1a1810"> <div class="card" style="margin-top:16px;border-color:#3d3520;background:#1a1810">
<p style="margin:0 0 8px;color:#ddd"> <p style="margin:0;color:#ddd">
<strong><%= t("billing.bank_transfer.pending_title") %></strong> <strong><%= t("billing.bank_transfer.pending_title") %></strong>
<%= raw t("billing.bank_transfer.pending_body_html", <%= raw t("billing.bank_transfer.pending_body_html",
plan: order.plan.name, plan: order.plan.name,
price: order.price_label, price: order.price_label) %>
causal: order.payment_causal) %>
</p>
<p style="margin:0;color:#aaa;font-size:0.9rem">
<%= raw t("billing.bank_transfer.pending_proof_html",
email_link: mail_to(MatchLiveTv.bank_transfer_proof_email, MatchLiveTv.bank_transfer_proof_email)) %>
</p> </p>
</div> </div>
@@ -109,7 +109,7 @@
public_club_billing_bank_transfer_path(club, plan: plan.slug, interval: interval), public_club_billing_bank_transfer_path(club, plan: plan.slug, interval: interval),
method: :post, method: :post,
class: "btn btn-outline plan-interval-btn", class: "btn btn-outline plan-interval-btn",
form: { data: { turbo_confirm: t("billing.bank_transfer.hint") } } %> form: { data: { turbo_confirm: t("billing.bank_transfer.confirm") } } %>
<% end %> <% end %>
</div> </div>
<p class="plan-action-hint" style="margin-top:8px"><%= t("billing.bank_transfer.hint") %></p> <p class="plan-action-hint" style="margin-top:8px"><%= t("billing.bank_transfer.hint") %></p>
@@ -1,11 +1,19 @@
<%# locals: (quote:) %> <%# locals: (quote:, subscription: nil) %>
<% return if quote.blank? %> <% return if quote.blank? %>
<% quoted_plan_active = current_paid_plan?(subscription, quote.plan) %>
<div class="card" style="margin-top:16px;border-color:#2e5a3c;background:#142018"> <div class="card" style="margin-top:16px;border-color:#2e5a3c;background:#142018">
<p style="margin:0;color:#ddd"> <p style="margin:0;color:#ddd">
<strong><%= t("billing.bank_transfer.quote_banner_title") %></strong> <% if quoted_plan_active %>
<%= raw t("billing.bank_transfer.quote_banner_body_html", plan: quote.plan.name, price: quote.price_label) %> <strong><%= t("billing.bank_transfer.quote_banner_active_title") %></strong>
<%= raw t(
"billing.bank_transfer.quote_banner_active_body_html",
plan: quote.plan.name,
price: quote.price_label,
date: subscription&.current_period_end.present? ? l_local(subscription.current_period_end.to_date) : t("billing.subscription_status.end_of_period")
) %>
<% else %>
<strong><%= t("billing.bank_transfer.quote_banner_title") %></strong>
<%= raw t("billing.bank_transfer.quote_banner_body_html", plan: quote.plan.name, price: quote.price_label) %>
<% end %>
</p> </p>
<% if quote.note.present? %>
<p style="margin:8px 0 0;color:#aaa;font-size:0.9rem"><%= t("billing.bank_transfer.quote_note", note: quote.note) %></p>
<% end %>
</div> </div>
@@ -97,6 +97,21 @@ module MatchLiveTv
bank_transfer_iban.present? && bank_transfer_account_holder.present? bank_transfer_iban.present? && bank_transfer_account_holder.present?
end end
def smtp_configured?
ENV["SMTP_ADDRESS"].present?
end
# In produzione senza SMTP non blocca il flusso (es. collaudo): la mail si può inviare a mano.
def deliver_mail(mail)
if Rails.env.production? && !smtp_configured?
Rails.logger.info("[mail] skip (SMTP assente): #{mail.subject}")
return false
end
mail.deliver_now
true
end
def privacy_controller_name def privacy_controller_name
ENV.fetch("PRIVACY_CONTROLLER_NAME", "Emiliano Frascaro") ENV.fetch("PRIVACY_CONTROLLER_NAME", "Emiliano Frascaro")
end end
+2
View File
@@ -309,6 +309,8 @@ it:
club: Società club: Società
plan: Piano plan: Piano
amount: Importo amount: Importo
holder: Intestatario
iban: IBAN
causal: Causale causal: Causale
requested: Richiesto requested: Richiesto
confirm_button: Conferma pagamento e attiva confirm_button: Conferma pagamento e attiva
+8 -5
View File
@@ -83,11 +83,9 @@ de:
first_team_name_placeholder: "z. B. Under 15" first_team_name_placeholder: "z. B. Under 15"
plan_label: "Starttarif (für den gesamten Verein)" plan_label: "Starttarif (für den gesamten Verein)"
plan_free: "Free — 1 Streaming-Konto pro Team, 1 Livestream" plan_free: "Free — 1 Streaming-Konto pro Team, 1 Livestream"
plan_light: Premium Light plan_light: "Premium Light — 59 €/Jahr"
plan_full: Premium Full plan_full: "Premium Full — 199 €/Jahr"
interval_label: Premium-Abrechnung plan_hint: "Karte, Überweisung oder monatliche Abrechnung wählst du danach auf der Abo-Seite. Ein kostenpflichtiger Tarif wird erst nach der Zahlung aktiv."
interval_yearly: "Jährlich — 59 €/Jahr (Light) oder 199 €/Jahr (Full)"
interval_monthly: "Monatlich — 7,90 €/Monat (Light) oder 24,90 €/Monat (Full)"
submit: Verein erstellen submit: Verein erstellen
billing: billing:
title: "Abonnement — %{name}" title: "Abonnement — %{name}"
@@ -193,6 +191,10 @@ de:
contact_trailer: für ein maßgeschneidertes Angebot. contact_trailer: für ein maßgeschneidertes Angebot.
club_payments_link: Zahlungen und Rechnungen des Vereins club_payments_link: Zahlungen und Rechnungen des Vereins
streaming_staff: streaming_staff:
principal_heading: Hauptkonto
principal_body_html: "Das Konto, mit dem der Verein registriert wurde (<strong>%{email}</strong>), ist <strong>für dieses Team vorgesehen</strong>: damit meldest du dich in der App an und startest den Livestream."
principal_score_hint_html: "Für den Punktestand von einem anderen Telefon teile während des Streams den <strong>Regie-Link</strong> (kein zweites Konto nötig)."
extra_staff_heading: Weitere Konten, die übertragen können
self_account_heading: "Dein Konto (%{email})" self_account_heading: "Dein Konto (%{email})"
self_account_hint: "Du kannst die Übertragung aus der App verwalten. Für den Punktestand von einem anderen Telefon teile den <strong>Regie-Link</strong> während des Streams (kein zweites Konto nötig)." self_account_hint: "Du kannst die Übertragung aus der App verwalten. Für den Punktestand von einem anderen Telefon teile den <strong>Regie-Link</strong> während des Streams (kein zweites Konto nötig)."
self_assign_submit: Das mache ich — Übertragungsverantwortlicher self_assign_submit: Das mache ich — Übertragungsverantwortlicher
@@ -613,6 +615,7 @@ de:
clubs: clubs:
already_registered: Du hast bereits einen Verein registriert already_registered: Du hast bereits einen Verein registriert
created: Verein und erstes Team erstellt. created: Verein und erstes Team erstellt.
created_complete_subscription: Verein erstellt. Schließe das Abo auf dieser Seite ab.
updated: Verein aktualisiert. updated: Verein aktualisiert.
complete_billing_first: Vervollständige deine Rechnungsdaten, bevor du einen Premium-Tarif aktivierst. complete_billing_first: Vervollständige deine Rechnungsdaten, bevor du einen Premium-Tarif aktivierst.
comped_change_denied: "Dieser Tarif ist ein von Match Live TV verwaltetes kostenloses Abonnement. Um zu einem kostenpflichtigen Abonnement zu wechseln, kontaktiere den Support." comped_change_denied: "Dieser Tarif ist ein von Match Live TV verwaltetes kostenloses Abonnement. Um zu einem kostenpflichtigen Abonnement zu wechseln, kontaktiere den Support."
+8 -5
View File
@@ -78,11 +78,9 @@ en:
first_team_name_placeholder: "e.g. Under 15" first_team_name_placeholder: "e.g. Under 15"
plan_label: "Starting plan (for the whole club)" plan_label: "Starting plan (for the whole club)"
plan_free: "Free — 1 transmitter account per team, 1 live stream" plan_free: "Free — 1 transmitter account per team, 1 live stream"
plan_light: Premium Light plan_light: "Premium Light — €59/year"
plan_full: Premium Full plan_full: "Premium Full — €199/year"
interval_label: Premium billing plan_hint: "Card, bank transfer or monthly billing are chosen later on the Subscription page. A paid plan activates only after payment."
interval_yearly: "Yearly — €59/year (Light) or €199/year (Full)"
interval_monthly: "Monthly — €7.90/month (Light) or €24.90/month (Full)"
submit: Create club submit: Create club
billing: billing:
title: "Subscription — %{name}" title: "Subscription — %{name}"
@@ -188,6 +186,10 @@ en:
contact_trailer: for a custom offer. contact_trailer: for a custom offer.
club_payments_link: Club payments and invoices club_payments_link: Club payments and invoices
streaming_staff: streaming_staff:
principal_heading: Main account
principal_body_html: "The account used to register the club (<strong>%{email}</strong>) is the one <strong>designated for this team</strong>: use it in the app to start the live stream."
principal_score_hint_html: "For scoring from another phone, share the <strong>control-room link</strong> during the stream (no second account needed)."
extra_staff_heading: Other accounts that can broadcast
self_account_heading: "Your account (%{email})" self_account_heading: "Your account (%{email})"
self_account_hint: "You can manage streaming from the app. For scoring from another phone, share the <strong>control-room link</strong> during the stream (no second account needed)." self_account_hint: "You can manage streaming from the app. For scoring from another phone, share the <strong>control-room link</strong> during the stream (no second account needed)."
self_assign_submit: I'll do it — broadcast manager self_assign_submit: I'll do it — broadcast manager
@@ -608,6 +610,7 @@ en:
clubs: clubs:
already_registered: You've already registered a club already_registered: You've already registered a club
created: Club and first team created. created: Club and first team created.
created_complete_subscription: Club created. Complete your subscription on this page.
updated: Club updated. updated: Club updated.
complete_billing_first: Complete your billing details before activating a premium plan. complete_billing_first: Complete your billing details before activating a premium plan.
comped_change_denied: "This plan is a complimentary subscription managed by Match Live TV. To switch to a paid subscription, contact support." comped_change_denied: "This plan is a complimentary subscription managed by Match Live TV. To switch to a paid subscription, contact support."
+8 -5
View File
@@ -83,11 +83,9 @@ es:
first_team_name_placeholder: "ej. Sub-15" first_team_name_placeholder: "ej. Sub-15"
plan_label: "Plan inicial (para todo el club)" plan_label: "Plan inicial (para todo el club)"
plan_free: "Free — 1 cuenta de emisión por equipo, 1 directo" plan_free: "Free — 1 cuenta de emisión por equipo, 1 directo"
plan_light: Premium Light plan_light: "Premium Light — 59 €/año"
plan_full: Premium Full plan_full: "Premium Full — 199 €/año"
interval_label: Facturación premium plan_hint: "La tarjeta, la transferencia o la facturación mensual se eligen después, en Abono. El plan de pago se activa solo tras el pago."
interval_yearly: "Anual — 59 €/año (Light) o 199 €/año (Full)"
interval_monthly: "Mensual — 7,90 €/mes (Light) o 24,90 €/mes (Full)"
submit: Crear club submit: Crear club
billing: billing:
title: "Suscripción — %{name}" title: "Suscripción — %{name}"
@@ -193,6 +191,10 @@ es:
contact_trailer: para una oferta a medida. contact_trailer: para una oferta a medida.
club_payments_link: Pagos y facturas del club club_payments_link: Pagos y facturas del club
streaming_staff: streaming_staff:
principal_heading: Cuenta principal
principal_body_html: "La cuenta con la que se registró el club (<strong>%{email}</strong>) es la <strong>designada para este equipo</strong>: con ella entras en la app y arrancas el directo."
principal_score_hint_html: "Para el marcador desde otro teléfono, durante el directo comparte el <strong>enlace de control</strong> (no hace falta una segunda cuenta)."
extra_staff_heading: Otras cuentas que pueden transmitir
self_account_heading: "Tu cuenta (%{email})" self_account_heading: "Tu cuenta (%{email})"
self_account_hint: "Puedes gestionar la transmisión desde la app. Para el marcador desde otro teléfono, comparte el <strong>enlace de control</strong> durante el directo (no hace falta una segunda cuenta)." self_account_hint: "Puedes gestionar la transmisión desde la app. Para el marcador desde otro teléfono, comparte el <strong>enlace de control</strong> durante el directo (no hace falta una segunda cuenta)."
self_assign_submit: Yo me encargo — responsable de transmisión self_assign_submit: Yo me encargo — responsable de transmisión
@@ -613,6 +615,7 @@ es:
clubs: clubs:
already_registered: Ya has registrado un club already_registered: Ya has registrado un club
created: Club y primer equipo creados. created: Club y primer equipo creados.
created_complete_subscription: Club creado. Completa la suscripción en esta página.
updated: Club actualizado. updated: Club actualizado.
complete_billing_first: Completa tus datos de facturación antes de activar un plan premium. complete_billing_first: Completa tus datos de facturación antes de activar un plan premium.
comped_change_denied: "Este plan es una suscripción de cortesía gestionada por Match Live TV. Para pasar a una suscripción de pago, contacta con soporte." comped_change_denied: "Este plan es una suscripción de cortesía gestionada por Match Live TV. Para pasar a una suscripción de pago, contacta con soporte."
+8 -5
View File
@@ -83,11 +83,9 @@ fr:
first_team_name_placeholder: "ex. Under 15" first_team_name_placeholder: "ex. Under 15"
plan_label: "Forfait de départ (pour tout le club)" plan_label: "Forfait de départ (pour tout le club)"
plan_free: "Gratuit — 1 compte diffuseur par équipe, 1 direct" plan_free: "Gratuit — 1 compte diffuseur par équipe, 1 direct"
plan_light: Premium Light plan_light: "Premium Light — 59 €/an"
plan_full: Premium Full plan_full: "Premium Full — 199 €/an"
interval_label: Facturation premium plan_hint: "Carte, virement ou mensualité se choisissent ensuite, sur la page Abonnement. Le forfait payant ne s'active qu'après le paiement."
interval_yearly: "Annuel — 59 €/an (Light) ou 199 €/an (Full)"
interval_monthly: "Mensuel — 7,90 €/mois (Light) ou 24,90 €/mois (Full)"
submit: Créer le club submit: Créer le club
billing: billing:
title: "Abonnement — %{name}" title: "Abonnement — %{name}"
@@ -193,6 +191,10 @@ fr:
contact_trailer: pour une offre sur mesure. contact_trailer: pour une offre sur mesure.
club_payments_link: Paiements et factures du club club_payments_link: Paiements et factures du club
streaming_staff: streaming_staff:
principal_heading: Compte principal
principal_body_html: "Le compte utilisé pour enregistrer le club (<strong>%{email}</strong>) est celui <strong>désigné pour cette équipe</strong> : cest avec lui que tu te connectes à lapp et que tu lances le direct."
principal_score_hint_html: "Pour le score depuis un autre téléphone, pendant le direct partage le <strong>lien régie</strong> (pas besoin dun second compte)."
extra_staff_heading: Autres comptes autorisés à diffuser
self_account_heading: "Ton compte (%{email})" self_account_heading: "Ton compte (%{email})"
self_account_hint: "Tu peux gérer la diffusion depuis l'application. Pour le score depuis un autre téléphone, partage le <strong>lien régie</strong> pendant le direct (pas besoin d'un second compte)." self_account_hint: "Tu peux gérer la diffusion depuis l'application. Pour le score depuis un autre téléphone, partage le <strong>lien régie</strong> pendant le direct (pas besoin d'un second compte)."
self_assign_submit: C'est moi — responsable diffusion self_assign_submit: C'est moi — responsable diffusion
@@ -613,6 +615,7 @@ fr:
clubs: clubs:
already_registered: Tu as déjà inscrit un club already_registered: Tu as déjà inscrit un club
created: Club et première équipe créés. created: Club et première équipe créés.
created_complete_subscription: Club créé. Termine l'abonnement sur cette page.
updated: Club mis à jour. updated: Club mis à jour.
complete_billing_first: Complète tes données de facturation avant d'activer un forfait premium. complete_billing_first: Complète tes données de facturation avant d'activer un forfait premium.
comped_change_denied: "Ce forfait est un abonnement offert géré par Match Live TV. Pour passer à un abonnement payant, contacte le support." comped_change_denied: "Ce forfait est un abonnement offert géré par Match Live TV. Pour passer à un abonnement payant, contacte le support."
+15 -10
View File
@@ -78,11 +78,9 @@ it:
first_team_name_placeholder: "es. Under 15" first_team_name_placeholder: "es. Under 15"
plan_label: "Piano iniziale (per tutta la società)" plan_label: "Piano iniziale (per tutta la società)"
plan_free: "Free — 1 account trasmettitore per squadra, 1 live" plan_free: "Free — 1 account trasmettitore per squadra, 1 live"
plan_light: Premium Light plan_light: "Premium Light — €59/anno"
plan_full: Premium Full plan_full: "Premium Full — €199/anno"
interval_label: Fatturazione premium plan_hint: "Carta, bonifico o cadenza mensile li scegli dopo, nella pagina Abbonamento. Il piano a pagamento si attiva solo dopo il pagamento."
interval_yearly: "Annuale — €59/anno (Light) o €199/anno (Full)"
interval_monthly: "Mensile — €7,90/mese (Light) o €24,90/mese (Full)"
submit: Crea società submit: Crea società
billing: billing:
title: "Abbonamento — %{name}" title: "Abbonamento — %{name}"
@@ -188,6 +186,10 @@ it:
contact_trailer: per un'offerta su misura. contact_trailer: per un'offerta su misura.
club_payments_link: Pagamenti e fatture della società club_payments_link: Pagamenti e fatture della società
streaming_staff: streaming_staff:
principal_heading: Account principale
principal_body_html: "L'account con cui è stata registrata la società (<strong>%{email}</strong>) è quello <strong>designato per questa squadra</strong>: con quello accedi all'app e avvii la diretta."
principal_score_hint_html: "Per il punteggio da un altro telefono, durante la diretta condividi il <strong>link regia</strong> (non serve un secondo account)."
extra_staff_heading: Altri account che possono trasmettere
self_account_heading: "Il tuo account (%{email})" self_account_heading: "Il tuo account (%{email})"
self_account_hint: "Puoi gestire la trasmissione dall'app. Per il punteggio da un altro telefono, condividi il <strong>link regia</strong> durante la diretta (non serve un secondo account)." self_account_hint: "Puoi gestire la trasmissione dall'app. Per il punteggio da un altro telefono, condividi il <strong>link regia</strong> durante la diretta (non serve un secondo account)."
self_assign_submit: Sono io — responsabile trasmissione self_assign_submit: Sono io — responsabile trasmissione
@@ -330,7 +332,7 @@ it:
sdi_or_pec: "Codice destinatario SDI (7 caratteri) o PEC" sdi_or_pec: "Codice destinatario SDI (7 caratteri) o PEC"
sdi_invalid: "Codice destinatario SDI (7 caratteri)" sdi_invalid: "Codice destinatario SDI (7 caratteri)"
actions: actions:
current_plan: Piano attuale current_plan: Piano attivo
contact_for_free: "Per passare al piano Free, contatta il supporto." contact_for_free: "Per passare al piano Free, contatta il supporto."
stripe_not_configured: Stripe non configurato stripe_not_configured: Stripe non configurato
stripe_prices_not_configured: Prezzi Stripe non configurati stripe_prices_not_configured: Prezzi Stripe non configurati
@@ -436,13 +438,15 @@ it:
pay_button: Paga con bonifico pay_button: Paga con bonifico
pay_button_with_price: "Paga con bonifico — %{price}" pay_button_with_price: "Paga con bonifico — %{price}"
or_label: oppure or_label: oppure
pending_title: Bonifico in attesa di conferma pending_title: Ordine in corso
pending_body_html: "Hai richiesto <strong>%{plan}</strong> (%{price}). Il piano si attiverà dopo la conferma del pagamento. Causale: <strong>%{causal}</strong>." pending_body_html: "Hai richiesto <strong>%{plan}</strong> (%{price}). Resta in attesa: il piano si attiverà quando Match Live TV confermerà il pagamento."
pending_proof_html: "Invia la distinta a %{email_link}."
quote_banner_title: Prezzo concordato quote_banner_title: Prezzo concordato
quote_banner_body_html: "Per questa società vale <strong>%{plan}</strong> a <strong>%{price}</strong>. Il pagamento avviene solo con bonifico (non con carta)." quote_banner_body_html: "Per questa società vale <strong>%{plan}</strong> a <strong>%{price}</strong>. Il pagamento avviene solo con bonifico (non con carta)."
quote_banner_active_title: Piano attivo
quote_banner_active_body_html: "In questo momento vale <strong>%{plan}</strong> a <strong>%{price}</strong>, fino al <strong>%{date}</strong>. Il rinnovo non è automatico."
quote_note: "Nota commerciale: %{note}" quote_note: "Nota commerciale: %{note}"
hint: "Il piano resta quello attuale finché Match Live TV non conferma l'accredito." hint: "Il piano resta quello attuale finché Match Live TV non conferma l'accredito."
confirm: "Confermi la richiesta di bonifico? Il piano resta quello attuale finché Match Live TV non conferma l'accredito."
renewal_html: "Con il bonifico l'abbonamento non si rinnova da solo: alla scadenza torna Free, salvo nuovo bonifico o rinnovo da parte nostra." renewal_html: "Con il bonifico l'abbonamento non si rinnova da solo: alla scadenza torna Free, salvo nuovo bonifico o rinnovo da parte nostra."
regia: regia:
meta_title_fallback: "Regia — Match Live TV" meta_title_fallback: "Regia — Match Live TV"
@@ -627,6 +631,7 @@ it:
clubs: clubs:
already_registered: Hai già registrato una società already_registered: Hai già registrato una società
created: Società e prima squadra create. created: Società e prima squadra create.
created_complete_subscription: Società creata. Completa l'abbonamento in questa pagina.
updated: Società aggiornata. updated: Società aggiornata.
complete_billing_first: Completa i dati di fatturazione prima di attivare un piano premium. complete_billing_first: Completa i dati di fatturazione prima di attivare un piano premium.
comped_change_denied: "Il piano è un abbonamento omaggio gestito da Match Live TV. Per passare a un abbonamento a pagamento contatta il supporto." comped_change_denied: "Il piano è un abbonamento omaggio gestito da Match Live TV. Per passare a un abbonamento a pagamento contatta il supporto."
@@ -646,7 +651,7 @@ it:
subscription_canceled: "Abbonamento disdetto. Resta attivo fino al %{date}; da quel giorno passerai al piano Free." subscription_canceled: "Abbonamento disdetto. Resta attivo fino al %{date}; da quel giorno passerai al piano Free."
stripe_error: "Errore Stripe: %{message}" stripe_error: "Errore Stripe: %{message}"
invoice_pdf_unavailable: PDF fattura non disponibile. invoice_pdf_unavailable: PDF fattura non disponibile.
bank_transfer_requested: Istruzioni per il bonifico inviate all'email di fatturazione. Il piano si attiverà dopo la conferma del pagamento. bank_transfer_requested: Richiesta registrata. Il piano si attiverà dopo la conferma del pagamento.
quote_checkout_denied: "Per questa società è attivo un prezzo concordato: usa il pagamento con bonifico." quote_checkout_denied: "Per questa società è attivo un prezzo concordato: usa il pagamento con bonifico."
club_recordings: club_recordings:
not_authorized: Non autorizzato not_authorized: Non autorizzato
+12 -9
View File
@@ -147,12 +147,13 @@
var hasSite = options.hasSite !== false; var hasSite = options.hasSite !== false;
var hasYoutube = !!options.hasYoutube; var hasYoutube = !!options.hasYoutube;
var isDelete = !!options.isDelete; var isDelete = !!options.isDelete;
var isReplayDelete = !!options.isReplayDelete;
var canSite = hasSite; var canSite = hasSite;
var canYoutube = hasYoutube; var canYoutube = hasYoutube;
var canBoth = hasSite && hasYoutube; var canBoth = hasSite && hasYoutube;
var choiceCount = (canSite ? 1 : 0) + (canYoutube ? 1 : 0) + (canBoth ? 1 : 0); var choiceCount = (canSite ? 1 : 0) + (canYoutube ? 1 : 0) + (canBoth ? 1 : 0);
// Una sola destinazione disponibile → conferma semplice, senza elenco // Una sola destinazione disponibile → conferma semplice, senza elenco
var multi = !!options.multi && choiceCount > 1; var multi = isReplayDelete && choiceCount > 1;
if (canBoth) pendingScope = "both"; if (canBoth) pendingScope = "both";
else if (canSite) pendingScope = "site"; else if (canSite) pendingScope = "site";
@@ -175,9 +176,9 @@
if (titleEl) { if (titleEl) {
if (multi) { if (multi) {
titleEl.textContent = I18N.multiDeleteTitle; titleEl.textContent = I18N.multiDeleteTitle;
} else if (pendingScope === "youtube") { } else if (isReplayDelete && pendingScope === "youtube") {
titleEl.textContent = I18N.deleteYoutubeTitle; titleEl.textContent = I18N.deleteYoutubeTitle;
} else if (pendingScope === "site") { } else if (isReplayDelete && pendingScope === "site") {
titleEl.textContent = I18N.deleteSiteTitle; titleEl.textContent = I18N.deleteSiteTitle;
} else { } else {
titleEl.textContent = isDelete ? I18N.deleteTitle : I18N.title; titleEl.textContent = isDelete ? I18N.deleteTitle : I18N.title;
@@ -187,9 +188,9 @@
if (messageEl) { if (messageEl) {
if (multi) { if (multi) {
messageEl.textContent = I18N.multiDeleteMessage; messageEl.textContent = I18N.multiDeleteMessage;
} else if (pendingScope === "youtube") { } else if (isReplayDelete && pendingScope === "youtube") {
messageEl.textContent = I18N.deleteYoutubeMessage; messageEl.textContent = I18N.deleteYoutubeMessage;
} else if (pendingScope === "site") { } else if (isReplayDelete && pendingScope === "site") {
messageEl.textContent = hasYoutube messageEl.textContent = hasYoutube
? I18N.deleteSiteMessageWithYoutube ? I18N.deleteSiteMessageWithYoutube
: I18N.deleteSiteMessageOnly; : I18N.deleteSiteMessageOnly;
@@ -203,8 +204,8 @@
if (okBtn instanceof HTMLElement) { if (okBtn instanceof HTMLElement) {
okBtn.hidden = multi; okBtn.hidden = multi;
if (pendingScope === "youtube") okBtn.textContent = I18N.deleteYoutubeOk; if (isReplayDelete && pendingScope === "youtube") okBtn.textContent = I18N.deleteYoutubeOk;
else if (pendingScope === "site") okBtn.textContent = I18N.deleteSiteOk; else if (isReplayDelete && pendingScope === "site") okBtn.textContent = I18N.deleteSiteOk;
else okBtn.textContent = isDelete ? I18N.deleteOk : I18N.confirmOk; else okBtn.textContent = isDelete ? I18N.deleteOk : I18N.confirmOk;
} }
@@ -302,12 +303,14 @@
form.getAttribute("data-has-youtube") === "1" || form.getAttribute("data-has-youtube") === "1" ||
mode === "delete-replay-youtube"; mode === "delete-replay-youtube";
var hasSiteAttr = form.getAttribute("data-has-site"); var hasSiteAttr = form.getAttribute("data-has-site");
var hasSite = hasSiteAttr == null ? true : hasSiteAttr === "1"; var hasSite = isReplayDelete
? hasSiteAttr == null || hasSiteAttr === "1"
: hasSiteAttr === "1";
var isDelete = var isDelete =
form.getAttribute("data-confirm-kind") === "delete" || isReplayDelete; form.getAttribute("data-confirm-kind") === "delete" || isReplayDelete;
openDialog(message, { openDialog(message, {
multi: isReplayDelete, isReplayDelete: isReplayDelete,
hasYoutube: hasYoutube, hasYoutube: hasYoutube,
hasSite: hasSite, hasSite: hasSite,
isDelete: isDelete isDelete: isDelete
@@ -76,4 +76,72 @@ RSpec.describe Public::BillingHelper, type: :helper do
) )
expect(action[:kind]).to eq(:none) expect(action[:kind]).to eq(:none)
end end
it "con prezzo concordato non pagato resta sul bonifico" do
quote = instance_double(Billing::ClubQuote, plan_slug: "premium_full", price_label: "€99/anno", billing_interval: "yearly")
club = instance_double(Club, active_billing_quote: quote)
subscription = instance_double(
Subscription,
active?: true,
premium?: false,
plan_change_pending?: false,
plan: Plan["free"]
)
action = helper.plan_billing_action(
current_slug: "free",
target_plan: premium_full,
stripe_subscription_active: false,
subscription: subscription,
club: club
)
expect(action[:kind]).to eq(:quoted)
expect(action[:quote]).to eq(quote)
end
it "con prezzo concordato già pagato mostra il piano attivo" do
quote = instance_double(Billing::ClubQuote, plan_slug: "premium_full", price_label: "€99/anno")
club = instance_double(Club, active_billing_quote: quote)
subscription = instance_double(
Subscription,
active?: true,
premium?: true,
plan_change_pending?: false,
plan: premium_full,
billing_interval: "yearly"
)
action = helper.plan_billing_action(
current_slug: "premium_full",
target_plan: premium_full,
stripe_subscription_active: false,
current_interval: "yearly",
subscription: subscription,
club: club
)
expect(action[:kind]).to eq(:current)
expect(action[:label]).to include("Piano attivo")
expect(action[:label]).to include("€99/anno")
end
it "con bonifico già attivo senza Stripe mostra il piano corrente" do
subscription = instance_double(
Subscription,
active?: true,
premium?: true,
plan_change_pending?: false,
plan: premium_full,
billing_interval: "yearly"
)
action = helper.plan_billing_action(
current_slug: "premium_full",
target_plan: premium_full,
stripe_subscription_active: false,
current_interval: "yearly",
subscription: subscription
)
expect(action[:kind]).to eq(:current)
expect(action[:label]).to include("Piano attivo")
end
end end
@@ -57,6 +57,33 @@ RSpec.describe "Public bank transfer billing", type: :request do
get public_club_billing_path(club) get public_club_billing_path(club)
expect(response.body).to include("Prezzo concordato") expect(response.body).to include("Prezzo concordato")
expect(response.body).to include("€120") expect(response.body).to include("€120")
expect(response.body).to include("Paga con bonifico")
expect(response.body).not_to include("Attiva Full — €199/anno") expect(response.body).not_to include("Attiva Full — €199/anno")
end end
it "con prezzo concordato già attivato mostra Piano attivo e nasconde il pagamento" do
Billing::SetClubQuote.upsert(
club: club, plan_slug: "premium_full", interval: "yearly",
amount_euros: "99", note: "Test", admin: nil
)
Billing::AssignPlan.call(
club: club,
plan_slug: "premium_full",
status: "active",
stripe_attrs: {
stripe_subscription_id: nil,
billing_interval: "yearly",
current_period_start: Time.current,
current_period_end: 1.year.from_now,
cancel_at_period_end: true
}
)
get public_club_billing_path(club)
expect(response.body).to include("Piano attivo")
expect(response.body).to include("In questo momento vale")
expect(response.body).to include("€99")
expect(response.body).not_to include("Paga con bonifico")
expect(response.body).not_to include("Il piano resta quello attuale finché Match Live TV non conferma")
end
end end
@@ -0,0 +1,56 @@
require "rails_helper"
RSpec.describe "Public club create", type: :request do
let!(:coach) do
User.create!(email: "create-coach@test.it", name: "Coach", password: "Password123", role: "coach")
end
def login!
post public_login_path, params: { email: coach.email, password: "Password123" }
end
def create_params(plan:)
{
club: {
name: "Nuova Società",
sport: "pallavolo",
primary_color: "#e53935",
secondary_color: "#ffffff"
},
first_team: { name: "Under 15" },
plan: plan
}
end
it "mostra solo il piano iniziale, senza cadenza" do
login!
get public_new_club_path
expect(response).to have_http_status(:ok)
expect(response.body).to include("Piano iniziale")
expect(response.body).not_to include("Fatturazione premium")
end
it "crea la società sul piano Free" do
login!
expect {
post public_clubs_path, params: create_params(plan: "free")
}.to change(Club, :count).by(1)
club = Club.order(:created_at).last
expect(response).to redirect_to(public_club_path(club))
expect(club.subscription.plan.slug).to eq("free")
team = club.teams.last
membership = coach.user_teams.find_by(team: team)
expect(membership&.staff_kind).to eq("transmission")
end
it "se sceglie Full senza Stripe resta Free e va in billing" do
login!
allow(MatchLiveTv).to receive(:stripe_enabled?).and_return(false)
post public_clubs_path, params: create_params(plan: "premium_full")
club = Club.order(:created_at).last
expect(club.subscription.plan.slug).to eq("free")
expect(response).to redirect_to(public_club_billing_path(club))
end
end
@@ -22,9 +22,13 @@ RSpec.describe "Public team transmission invite", type: :request do
get public_team_details_path(team) get public_team_details_path(team)
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(response.body).to include("Account principale")
expect(response.body).to include("designato per questa squadra")
expect(response.body).to include(coach.email)
expect(response.body).to include("Invita un account a trasmettere") expect(response.body).to include("Invita un account a trasmettere")
expect(response.body).to include('id="invita-trasmissione"') expect(response.body).to include('id="invita-trasmissione"')
expect(response.body).to include("#responsabili-trasmissione") expect(response.body).not_to include("Sono io — responsabile trasmissione")
expect(response.body).not_to include("Nessun responsabile trasmissione")
end end
it "GET /teams/:id/invite reindirizza ai dettagli con ancora" do it "GET /teams/:id/invite reindirizza ai dettagli con ancora" do
@@ -26,11 +26,11 @@ RSpec.describe Billing::RequestBankTransfer do
) )
end end
it "crea un ordine a listino e invia le istruzioni" do it "crea l'ordine a listino senza inviare le istruzioni" do
expect { expect {
described_class.call(club: club, user: user, plan_slug: "premium_light", interval: "yearly") described_class.call(club: club, user: user, plan_slug: "premium_light", interval: "yearly")
}.to change { club.billing_transfer_orders.count }.by(1) }.to change { club.billing_transfer_orders.count }.by(1)
.and change { ActionMailer::Base.deliveries.size }.by(1) .and change { ActionMailer::Base.deliveries.size }.by(0)
order = club.billing_transfer_orders.last order = club.billing_transfer_orders.last
expect(order).to be_awaiting_payment expect(order).to be_awaiting_payment
@@ -41,6 +41,16 @@ RSpec.describe Billing::RequestBankTransfer do
expect(club.reload.subscription.plan.slug).to eq("free") expect(club.reload.subscription.plan.slug).to eq("free")
end end
it "non reinvia istruzioni se l'ordine è già in attesa" do
first = described_class.call(club: club, user: user, plan_slug: "premium_light", interval: "yearly")
ActionMailer::Base.deliveries.clear
expect {
second = described_class.call(club: club, user: user, plan_slug: "premium_light", interval: "yearly")
expect(second.id).to eq(first.id)
}.not_to change { ActionMailer::Base.deliveries.size }
end
it "usa l'importo concordato se presente" do it "usa l'importo concordato se presente" do
Billing::SetClubQuote.upsert( Billing::SetClubQuote.upsert(
club: club, plan_slug: "premium_full", interval: "yearly", club: club, plan_slug: "premium_full", interval: "yearly",
@@ -19,6 +19,12 @@ RSpec.describe Teams::StaffAssignment do
expect(team.entitlements.staff_count_for("transmission")).to eq(1) expect(team.entitlements.staff_count_for("transmission")).to eq(1)
end end
it "designa il titolare della società sulla squadra" do
described_class.designate_club_owner!(team: team, user: owner)
membership = owner.user_teams.find_by!(team: team)
expect(membership.staff_kind).to eq("transmission")
end
it "rejects duplicate email for transmission" do it "rejects duplicate email for transmission" do
UserTeam.create!(user: other, team: team, role: "member", staff_kind: "transmission") UserTeam.create!(user: other, team: team, role: "member", staff_kind: "transmission")
expect { expect {