From 82ff1972a0b3e1dfaa46e5629b390a8805976707 Mon Sep 17 00:00:00 2001
From: Emiliano Frascaro
Date: Tue, 2 Jun 2026 22:26:19 +0200
Subject: [PATCH] Aggiunge abbonamenti omaggio admin per sponsor e promozioni.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Gli admin possono concedere o revocare Premium Light/Full senza Stripe;
il piano omaggio ha priorità sui webhook e la società vede un messaggio dedicato.
Co-authored-by: Cursor
---
.../app/controllers/admin/clubs_controller.rb | 45 ++++++++
.../controllers/public/clubs_controller.rb | 6 ++
backend/app/models/subscription.rb | 5 +
.../billing/admin_comped_subscription.rb | 101 ++++++++++++++++++
.../services/billing/stripe/change_plan.rb | 1 +
.../services/billing/stripe/checkout_sync.rb | 2 +
.../billing/stripe/finalize_subscription.rb | 1 +
.../app/views/admin/billing/index.html.erb | 7 ++
.../views/admin/clubs/_comped_form.html.erb | 56 ++++++++++
backend/app/views/admin/clubs/index.html.erb | 38 +++++++
backend/app/views/admin/clubs/show.html.erb | 16 +++
backend/app/views/layouts/admin.html.erb | 1 +
.../app/views/public/clubs/billing.html.erb | 24 ++++-
.../shared/_club_subscription_status.html.erb | 3 +
backend/config/routes.rb | 6 +-
...20000_add_admin_comped_to_subscriptions.rb | 10 ++
backend/db/schema.rb | 8 +-
backend/public/admin.css | 18 ++++
.../billing/admin_comped_subscription_spec.rb | 36 +++++++
.../flutter/generated_plugin_registrant.cc | 4 +
mobile/linux/flutter/generated_plugins.cmake | 1 +
.../Flutter/GeneratedPluginRegistrant.swift | 2 +
.../flutter/generated_plugin_registrant.cc | 3 +
.../windows/flutter/generated_plugins.cmake | 1 +
24 files changed, 388 insertions(+), 7 deletions(-)
create mode 100644 backend/app/controllers/admin/clubs_controller.rb
create mode 100644 backend/app/services/billing/admin_comped_subscription.rb
create mode 100644 backend/app/views/admin/clubs/_comped_form.html.erb
create mode 100644 backend/app/views/admin/clubs/index.html.erb
create mode 100644 backend/app/views/admin/clubs/show.html.erb
create mode 100644 backend/db/migrate/20260603120000_add_admin_comped_to_subscriptions.rb
create mode 100644 backend/spec/services/billing/admin_comped_subscription_spec.rb
diff --git a/backend/app/controllers/admin/clubs_controller.rb b/backend/app/controllers/admin/clubs_controller.rb
new file mode 100644
index 0000000..08570eb
--- /dev/null
+++ b/backend/app/controllers/admin/clubs_controller.rb
@@ -0,0 +1,45 @@
+module Admin
+ class ClubsController < BaseController
+ before_action :set_club, only: %i[show grant_comped revoke_comped]
+
+ def index
+ @clubs = Club.includes(:subscription, subscription: :plan, subscription: :admin_comped_by)
+ .order(:name)
+ end
+
+ def show
+ @subscription = @club.subscription || @club.build_subscription(plan: Plan["free"], status: "active")
+ @plans = Plan.ordered.reject { |p| p.slug == "free" }
+ end
+
+ def grant_comped
+ Billing::AdminCompedSubscription.grant(
+ club: @club,
+ plan_slug: params.require(:plan_slug),
+ reason: params[:reason],
+ admin: current_admin_account
+ )
+ redirect_back_or_club notice: "Abbonamento omaggio #{Plan[params[:plan_slug]].name} attivato per #{@club.name}."
+ rescue Billing::AdminCompedSubscription::Error, ActiveRecord::RecordInvalid => e
+ redirect_back_or_club alert: e.message
+ end
+
+ def revoke_comped
+ Billing::AdminCompedSubscription.revoke(club: @club, admin: current_admin_account)
+ redirect_back_or_club notice: "Abbonamento omaggio revocato per #{@club.name}."
+ rescue Billing::AdminCompedSubscription::Error => e
+ redirect_back_or_club alert: e.message
+ end
+
+ private
+
+ def set_club
+ @club = Club.find(params[:id])
+ end
+
+ def redirect_back_or_club(notice: nil, alert: nil)
+ target = params[:return_to].presence || admin_club_path(@club)
+ redirect_to target, notice: notice, alert: alert
+ end
+ end
+end
diff --git a/backend/app/controllers/public/clubs_controller.rb b/backend/app/controllers/public/clubs_controller.rb
index 7f29262..971933e 100644
--- a/backend/app/controllers/public/clubs_controller.rb
+++ b/backend/app/controllers/public/clubs_controller.rb
@@ -85,6 +85,12 @@ module Public
def checkout
require_club_owner!(@club)
+ if @club.subscription&.admin_comped?
+ redirect_to public_club_billing_path(@club),
+ alert: "Il piano è un abbonamento omaggio gestito da Match Live TV. Per passare a un abbonamento a pagamento contatta il supporto."
+ return
+ end
+
unless MatchLiveTv.stripe_enabled?
redirect_to public_club_billing_path(@club), alert: "Pagamenti non ancora configurati sul server"
return
diff --git a/backend/app/models/subscription.rb b/backend/app/models/subscription.rb
index 22622aa..6305f00 100644
--- a/backend/app/models/subscription.rb
+++ b/backend/app/models/subscription.rb
@@ -5,6 +5,7 @@ class Subscription < ApplicationRecord
belongs_to :club
belongs_to :plan
belongs_to :pending_plan, class_name: "Plan", optional: true
+ belongs_to :admin_comped_by, class_name: "AdminAccount", optional: true
validates :status, inclusion: { in: STATUSES }
validates :club_id, uniqueness: true
@@ -30,4 +31,8 @@ class Subscription < ApplicationRecord
def plan_change_pending?
pending_plan_id.present?
end
+
+ def admin_comped?
+ admin_comped
+ end
end
diff --git a/backend/app/services/billing/admin_comped_subscription.rb b/backend/app/services/billing/admin_comped_subscription.rb
new file mode 100644
index 0000000..79cab3c
--- /dev/null
+++ b/backend/app/services/billing/admin_comped_subscription.rb
@@ -0,0 +1,101 @@
+module Billing
+ class AdminCompedSubscription
+ class Error < StandardError; end
+
+ VALID_PLANS = %w[premium_light premium_full].freeze
+
+ def self.grant(club:, plan_slug:, reason:, admin:)
+ new(club: club, plan_slug: plan_slug, reason: reason, admin: admin).grant
+ end
+
+ def self.revoke(club:, admin:)
+ new(club: club, admin: admin).revoke
+ end
+
+ def initialize(club:, plan_slug: nil, reason: nil, admin: nil)
+ @club = club
+ @plan_slug = plan_slug.to_s.presence
+ @reason = reason.to_s.strip.presence
+ @admin = admin
+ end
+
+ def grant
+ raise Error, "Piano non valido" unless @plan_slug.in?(VALID_PLANS)
+
+ release_stripe_schedule!
+
+ AssignPlan.call(
+ club: @club,
+ plan_slug: @plan_slug,
+ status: "active",
+ stripe_attrs: comped_attrs.merge(
+ pending_plan_id: nil,
+ pending_billing_interval: nil,
+ stripe_schedule_id: nil,
+ cancel_at_period_end: false
+ )
+ )
+ end
+
+ def revoke
+ sub = @club.subscription
+ raise Error, "Nessun abbonamento omaggio attivo" unless sub&.admin_comped?
+
+ if sub.stripe_subscription_id.present? && MatchLiveTv.stripe_enabled?
+ stripe_sub = ::Stripe::Subscription.retrieve(sub.stripe_subscription_id)
+ if stripe_sub.status.in?(%w[active trialing past_due])
+ Billing::Stripe::CheckoutSync.from_subscription(stripe_sub, club: @club)
+ clear_comped_flags!(sub.reload)
+ return sub
+ end
+ end
+
+ AssignPlan.call(
+ club: @club,
+ plan_slug: "free",
+ status: "active",
+ stripe_attrs: {
+ admin_comped: false,
+ admin_comped_reason: nil,
+ admin_comped_at: nil,
+ admin_comped_by_id: nil,
+ pending_plan_id: nil,
+ pending_billing_interval: nil,
+ stripe_schedule_id: nil
+ }
+ )
+ end
+
+ private
+
+ def comped_attrs
+ {
+ admin_comped: true,
+ admin_comped_reason: @reason,
+ admin_comped_at: Time.current,
+ admin_comped_by_id: @admin&.id
+ }
+ end
+
+ def clear_comped_flags!(sub)
+ sub.update!(
+ admin_comped: false,
+ admin_comped_reason: nil,
+ admin_comped_at: nil,
+ admin_comped_by_id: nil
+ )
+ end
+
+ def release_stripe_schedule!
+ sub = @club.subscription
+ return if sub.blank? || sub.stripe_schedule_id.blank?
+ return unless MatchLiveTv.stripe_enabled?
+
+ ::Stripe::SubscriptionSchedule.release(sub.stripe_schedule_id)
+ rescue ::Stripe::InvalidRequestError
+ nil
+ ensure
+ sub&.update!(stripe_schedule_id: nil, pending_plan_id: nil, pending_billing_interval: nil)
+ end
+ end
+end
diff --git a/backend/app/services/billing/stripe/change_plan.rb b/backend/app/services/billing/stripe/change_plan.rb
index ff8ea30..71a498e 100644
--- a/backend/app/services/billing/stripe/change_plan.rb
+++ b/backend/app/services/billing/stripe/change_plan.rb
@@ -18,6 +18,7 @@ module Billing
raise "Stripe non configurato" unless MatchLiveTv.stripe_enabled?
raise "Piano non valido" unless VALID_PLANS.include?(@plan_slug)
+ raise "Abbonamento omaggio: contatta il supporto per cambiare piano a pagamento" if @sub&.admin_comped?
raise "Nessun abbonamento Stripe attivo" if @sub.blank? || @sub.stripe_subscription_id.blank?
interval = resolve_interval(@sub)
diff --git a/backend/app/services/billing/stripe/checkout_sync.rb b/backend/app/services/billing/stripe/checkout_sync.rb
index 684daa4..32011d7 100644
--- a/backend/app/services/billing/stripe/checkout_sync.rb
+++ b/backend/app/services/billing/stripe/checkout_sync.rb
@@ -70,6 +70,8 @@ module Billing
PriceCatalog::DEFAULT_INTERVAL
sub = club.subscription
+ return sub if sub&.admin_comped?
+
clear_pending = sub&.pending_plan_id.present? &&
(sub.pending_plan.slug == plan.slug || infer_plan_slug_from_price(stripe_sub) == sub.pending_plan.slug)
diff --git a/backend/app/services/billing/stripe/finalize_subscription.rb b/backend/app/services/billing/stripe/finalize_subscription.rb
index 20a1891..b781ae7 100644
--- a/backend/app/services/billing/stripe/finalize_subscription.rb
+++ b/backend/app/services/billing/stripe/finalize_subscription.rb
@@ -14,6 +14,7 @@ module Billing
sub = @club.subscription
return false unless sub&.stripe_subscription_id.present?
return false if sub.free?
+ return false if sub.admin_comped?
stripe_sub = ::Stripe::Subscription.retrieve(sub.stripe_subscription_id)
diff --git a/backend/app/views/admin/billing/index.html.erb b/backend/app/views/admin/billing/index.html.erb
index e547685..7fa8644 100644
--- a/backend/app/views/admin/billing/index.html.erb
+++ b/backend/app/views/admin/billing/index.html.erb
@@ -13,6 +13,13 @@
<% end %>
+<% if @filter_club %>
+ <%= render "admin/clubs/comped_form",
+ club: @filter_club,
+ subscription: @filter_club.subscription,
+ return_to: admin_billing_path(club_id: @filter_club.id) %>
+<% end %>
+
<% if @pending_payments.any? %>
<% @pending_payments.each do |payment| %>
diff --git a/backend/app/views/admin/clubs/_comped_form.html.erb b/backend/app/views/admin/clubs/_comped_form.html.erb
new file mode 100644
index 0000000..7e7b09f
--- /dev/null
+++ b/backend/app/views/admin/clubs/_comped_form.html.erb
@@ -0,0 +1,56 @@
+<%# locals: (club:, subscription:, return_to: nil) %>
+
+ Abbonamento omaggio
+
+ Sponsor o promozione: assegna Premium Light/Full senza pagamento Stripe. Revocabile in qualsiasi momento.
+
+
+ <% sub = subscription %>
+ <% if sub&.admin_comped? %>
+
+ Attivo: <%= sub.plan.name %>
+ <% if sub.admin_comped_reason.present? %>
+ · <%= sub.admin_comped_reason %>
+ <% end %>
+ <% if sub.admin_comped_at.present? %>
+ Dal <%= l(sub.admin_comped_at, format: :long) %>
+ <% if sub.admin_comped_by.present? %> · admin <%= sub.admin_comped_by.username %><% end %>
+
+ <% end %>
+ <% if sub.stripe_subscription_id.present? %>
+ Nota: esiste anche un abbonamento Stripe collegato; l’omaggio ha priorità sul piano.
+ <% end %>
+
+ <%= button_to "Revoca omaggio (torna Free o ripristina Stripe)",
+ revoke_comped_admin_club_path(club, return_to: return_to),
+ method: :delete,
+ class: "admin-btn admin-btn--secondary",
+ form: { data: { turbo_confirm: "Revocare l’abbonamento omaggio per #{club.name}?" } } %>
+ <% else %>
+
+ Piano attuale: <%= sub&.plan&.name || "Free" %>
+ <% if sub&.stripe_subscription_id.present? %>
+ · pagamento Stripe attivo
+ <% end %>
+
+ <%= form_with url: grant_comped_admin_club_path(club), method: :post, local: true, class: "admin-comped-form" do %>
+ <%= hidden_field_tag :return_to, return_to if return_to.present? %>
+
+
+ Piano
+ <%= select_tag :plan_slug,
+ options_for_select(
+ [["Premium Light", "premium_light"], ["Premium Full", "premium_full"]]
+ ),
+ required: true,
+ class: "admin-input" %>
+
+
+ Motivo (es. sponsor 2026)
+ <%= text_field_tag :reason, nil, placeholder: "Sponsor, promozione…", class: "admin-input", style: "width:100%" %>
+
+ <%= submit_tag "Concedi omaggio", class: "admin-btn admin-btn--primary" %>
+
+ <% end %>
+ <% end %>
+
diff --git a/backend/app/views/admin/clubs/index.html.erb b/backend/app/views/admin/clubs/index.html.erb
new file mode 100644
index 0000000..a09d359
--- /dev/null
+++ b/backend/app/views/admin/clubs/index.html.erb
@@ -0,0 +1,38 @@
+
Società
+
+ Gestisci abbonamenti omaggio (sponsor / promozioni). Per pagamenti Stripe usa <%= link_to "Fatturazione", admin_billing_path %>.
+
+
+
+
+
+ Società
+ Piano
+ Omaggio
+ Stripe
+
+
+
+
+ <% @clubs.each do |club| %>
+ <% sub = club.subscription %>
+
+ <%= club.name %>
+ <%= sub&.plan&.name || "Free" %>
+
+ <% if sub&.admin_comped? %>
+ Sì
+ <% if sub.admin_comped_reason.present? %> · <%= sub.admin_comped_reason %><% end %>
+ <% else %>
+ —
+ <% end %>
+
+ <%= sub&.stripe_subscription_id.present? ? "Sì" : "—" %>
+
+ <%= link_to "Gestisci", admin_club_path(club) %>
+ · <%= link_to "Fatture", admin_billing_path(club_id: club.id) %>
+
+
+ <% end %>
+
+
diff --git a/backend/app/views/admin/clubs/show.html.erb b/backend/app/views/admin/clubs/show.html.erb
new file mode 100644
index 0000000..bdba052
--- /dev/null
+++ b/backend/app/views/admin/clubs/show.html.erb
@@ -0,0 +1,16 @@
+
<%= link_to "← Tutte le società", admin_clubs_path %>
+
+
<%= @club.name %>
+
+ Sport: <%= @club.sport %>
+ · <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @club.id) %>
+
+
+<%= render "admin/clubs/comped_form", club: @club, subscription: @subscription, return_to: admin_club_path(@club) %>
+
+
Squadre
+
+ <% @club.teams.order(:name).each do |team| %>
+ <%= link_to team.name, admin_team_path(team) %>
+ <% end %>
+
diff --git a/backend/app/views/layouts/admin.html.erb b/backend/app/views/layouts/admin.html.erb
index 86bc16a..78037ca 100644
--- a/backend/app/views/layouts/admin.html.erb
+++ b/backend/app/views/layouts/admin.html.erb
@@ -17,6 +17,7 @@
<% if admin_logged_in? %>
<%= link_to "Dashboard", admin_root_path, class: ("active" if controller_name == "dashboard") %>
<%= link_to "Teams", admin_teams_path, class: ("active" if controller_name == "teams") %>
+ <%= link_to "Società", admin_clubs_path, class: ("active" if controller_name == "clubs") %>
<%= link_to "Fatturazione", admin_billing_path, class: ("active" if controller_name.in?(%w[billing billing_invoices])) %>
<%= link_to "YouTube", admin_youtube_platform_path, class: ("active" if controller_name == "youtube") %>
<%= link_to "Sessions", admin_sessions_path, class: ("active" if controller_name == "sessions") %>
diff --git a/backend/app/views/public/clubs/billing.html.erb b/backend/app/views/public/clubs/billing.html.erb
index b54662a..7bd9aaa 100644
--- a/backend/app/views/public/clubs/billing.html.erb
+++ b/backend/app/views/public/clubs/billing.html.erb
@@ -5,12 +5,26 @@
Abbonamento
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription, on_billing_page: true %>
- <%= render "shared/stripe_secure_payment" %>
- <% if MatchLiveTv.stripe_enabled? %>
- <%= render "shared/plan_change_info", subscription: @subscription %>
+ <% if @subscription&.admin_comped? %>
+
+
+ Abbonamento omaggio Match Live TV
+ — piano <%= @subscription.plan.name %>
+ <% if @subscription.admin_comped_reason.present? %>
+ (<%= @subscription.admin_comped_reason %>)
+ <% end %>.
+ Non è richiesto alcun pagamento. Per modifiche o passaggio a un piano a pagamento, contatta
+ <%= mail_to "info@matchlive.it", "info@matchlive.it" %>.
+
+
+ <% else %>
+ <%= render "shared/stripe_secure_payment" %>
+ <% if MatchLiveTv.stripe_enabled? %>
+ <%= render "shared/plan_change_info", subscription: @subscription %>
+ <% end %>
+ <%= render "shared/plan_cards", show_stripe_portal: false %>
+ <%= render "shared/subscription_cancel", club: @club, subscription: @subscription %>
<% end %>
- <%= render "shared/plan_cards", show_stripe_portal: false %>
- <%= render "shared/subscription_cancel", club: @club, subscription: @subscription %>
<%= render "shared/billing_documents", club: @club, payments: @payments %>
diff --git a/backend/app/views/shared/_club_subscription_status.html.erb b/backend/app/views/shared/_club_subscription_status.html.erb
index 68d6f36..3a92aac 100644
--- a/backend/app/views/shared/_club_subscription_status.html.erb
+++ b/backend/app/views/shared/_club_subscription_status.html.erb
@@ -3,6 +3,9 @@
Società: <%= club.name %>
· Piano attuale: <%= current_plan.name %>
+ <% if subscription&.admin_comped? %>
+ (omaggio Match Live TV)
+ <% end %>
(valido per tutte le squadre)
<% unless local_assigns[:on_billing_page] %>
· <%= link_to "Gestisci abbonamento", public_club_billing_path(club) %>
diff --git a/backend/config/routes.rb b/backend/config/routes.rb
index 850eb76..1a72663 100644
--- a/backend/config/routes.rb
+++ b/backend/config/routes.rb
@@ -68,7 +68,11 @@ Rails.application.routes.draw do
get "billing", to: "billing#index", as: :billing
post "billing/payments/:payment_id/attach_pdf", to: "billing#attach_pdf", as: :billing_payment_attach_pdf
resources :teams, only: %i[index show]
- resources :clubs, only: [] do
+ resources :clubs, only: %i[index show] do
+ member do
+ post :grant_comped
+ delete :revoke_comped
+ end
resources :billing_invoices, only: %i[index new create edit update], controller: "billing_invoices"
end
resources :sessions, only: %i[index show] do
diff --git a/backend/db/migrate/20260603120000_add_admin_comped_to_subscriptions.rb b/backend/db/migrate/20260603120000_add_admin_comped_to_subscriptions.rb
new file mode 100644
index 0000000..c6c3225
--- /dev/null
+++ b/backend/db/migrate/20260603120000_add_admin_comped_to_subscriptions.rb
@@ -0,0 +1,10 @@
+class AddAdminCompedToSubscriptions < ActiveRecord::Migration[7.2]
+ def change
+ change_table :subscriptions, bulk: true do |t|
+ t.boolean :admin_comped, null: false, default: false
+ t.string :admin_comped_reason
+ t.datetime :admin_comped_at
+ t.references :admin_comped_by, type: :uuid, foreign_key: { to_table: :admin_accounts }
+ end
+ end
+end
diff --git a/backend/db/schema.rb b/backend/db/schema.rb
index 9bed993..0cd4f4a 100644
--- a/backend/db/schema.rb
+++ b/backend/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2026_06_02_120000) do
+ActiveRecord::Schema[7.2].define(version: 2026_06_03_120000) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@@ -253,6 +253,11 @@ ActiveRecord::Schema[7.2].define(version: 2026_06_02_120000) do
t.uuid "pending_plan_id"
t.string "pending_billing_interval"
t.string "stripe_schedule_id"
+ t.boolean "admin_comped", default: false, null: false
+ t.string "admin_comped_reason"
+ t.datetime "admin_comped_at"
+ t.uuid "admin_comped_by_id"
+ t.index ["admin_comped_by_id"], name: "index_subscriptions_on_admin_comped_by_id"
t.index ["club_id"], name: "index_subscriptions_on_club_id", unique: true
t.index ["pending_plan_id"], name: "index_subscriptions_on_pending_plan_id"
t.index ["plan_id"], name: "index_subscriptions_on_plan_id"
@@ -355,6 +360,7 @@ ActiveRecord::Schema[7.2].define(version: 2026_06_02_120000) do
add_foreign_key "stream_events", "stream_sessions"
add_foreign_key "stream_sessions", "matches"
add_foreign_key "stream_sessions", "users"
+ add_foreign_key "subscriptions", "admin_accounts", column: "admin_comped_by_id"
add_foreign_key "subscriptions", "clubs"
add_foreign_key "subscriptions", "plans"
add_foreign_key "subscriptions", "plans", column: "pending_plan_id"
diff --git a/backend/public/admin.css b/backend/public/admin.css
index b0ea784..fc59c29 100644
--- a/backend/public/admin.css
+++ b/backend/public/admin.css
@@ -233,6 +233,24 @@ body.admin-body {
align-items: center;
}
+.admin-input {
+ padding: 0.45rem 0.6rem;
+ border: 1px solid #444;
+ border-radius: 6px;
+ background: #1a1a22;
+ color: #eee;
+ font-size: 0.9rem;
+}
+
+.admin-btn--secondary {
+ background: #333;
+ color: #eee;
+}
+
+.admin-btn--secondary:hover {
+ background: #444;
+}
+
.admin-btn {
display: inline-block;
padding: 0.45rem 0.9rem;
diff --git a/backend/spec/services/billing/admin_comped_subscription_spec.rb b/backend/spec/services/billing/admin_comped_subscription_spec.rb
new file mode 100644
index 0000000..938546a
--- /dev/null
+++ b/backend/spec/services/billing/admin_comped_subscription_spec.rb
@@ -0,0 +1,36 @@
+require "rails_helper"
+
+RSpec.describe Billing::AdminCompedSubscription do
+ let(:admin) { AdminAccount.create!(username: "ops", password: "secret123") }
+ let(:club) { Club.create!(name: "Sponsor FC", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
+
+ before do
+ load Rails.root.join("db/seeds/plans.rb")
+ Billing::AssignPlan.call(club: club, plan_slug: "free")
+ end
+
+ it "concede premium full senza stripe" do
+ described_class.grant(
+ club: club,
+ plan_slug: "premium_full",
+ reason: "Sponsor stagione 2026",
+ admin: admin
+ )
+
+ sub = club.reload.subscription
+ expect(sub.plan.slug).to eq("premium_full")
+ expect(sub.admin_comped?).to be true
+ expect(sub.admin_comped_reason).to eq("Sponsor stagione 2026")
+ expect(sub.admin_comped_by).to eq(admin)
+ end
+
+ it "revoca omaggio e torna a free" do
+ described_class.grant(club: club, plan_slug: "premium_light", reason: "Promo", admin: admin)
+
+ described_class.revoke(club: club, admin: admin)
+
+ sub = club.reload.subscription
+ expect(sub.plan.slug).to eq("free")
+ expect(sub.admin_comped?).to be false
+ end
+end
diff --git a/mobile/linux/flutter/generated_plugin_registrant.cc b/mobile/linux/flutter/generated_plugin_registrant.cc
index f6f23bf..3792af4 100644
--- a/mobile/linux/flutter/generated_plugin_registrant.cc
+++ b/mobile/linux/flutter/generated_plugin_registrant.cc
@@ -6,9 +6,13 @@
#include "generated_plugin_registrant.h"
+#include
#include
void fl_register_plugins(FlPluginRegistry* registry) {
+ g_autoptr(FlPluginRegistrar) gtk_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
+ gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
diff --git a/mobile/linux/flutter/generated_plugins.cmake b/mobile/linux/flutter/generated_plugins.cmake
index df8d2f7..21d8f8b 100644
--- a/mobile/linux/flutter/generated_plugins.cmake
+++ b/mobile/linux/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ gtk
url_launcher_linux
)
diff --git a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift
index 7a7aff1..29cf942 100644
--- a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
+import app_links
import battery_plus
import connectivity_plus
import mobile_scanner
@@ -15,6 +16,7 @@ import url_launcher_macos
import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
diff --git a/mobile/windows/flutter/generated_plugin_registrant.cc b/mobile/windows/flutter/generated_plugin_registrant.cc
index 4443ebc..df8aa87 100644
--- a/mobile/windows/flutter/generated_plugin_registrant.cc
+++ b/mobile/windows/flutter/generated_plugin_registrant.cc
@@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
+#include
#include
#include
#include
@@ -13,6 +14,8 @@
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
+ AppLinksPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
BatteryPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
diff --git a/mobile/windows/flutter/generated_plugins.cmake b/mobile/windows/flutter/generated_plugins.cmake
index 4a8c71a..861f51c 100644
--- a/mobile/windows/flutter/generated_plugins.cmake
+++ b/mobile/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ app_links
battery_plus
connectivity_plus
permission_handler_windows