Aggiunge pagamento con bonifico e importo concordato per società.

Il piano si attiva solo dopo la conferma admin; Stripe resta a listino se non c'è un prezzo commerciale.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 23:20:43 +02:00
co-authored by Cursor
parent 1e4e0560f1
commit e436f5ece4
56 changed files with 1571 additions and 53 deletions
@@ -0,0 +1,95 @@
module Billing
class RequestBankTransfer
class Error < StandardError; end
PLAN_SLUGS = %w[premium_light premium_full].freeze
def self.call(club:, user:, plan_slug:, interval:)
new(club: club, user: user, plan_slug: plan_slug, interval: interval).call
end
def initialize(club:, user:, plan_slug:, interval:)
@club = club
@user = user
@plan_slug = plan_slug.to_s
@interval = interval
end
def call
raise Error, "Bonifico non configurato sul server" unless MatchLiveTv.bank_transfer_configured?
raise Error, "Piano non valido" unless @plan_slug.in?(PLAN_SLUGS)
raise Error, "Completa i dati di fatturazione prima di richiedere il bonifico." unless @club.billing_profile_complete?
raise Error, "Il piano è un abbonamento omaggio. Contatta il supporto per passarlo a pagamento." if @club.subscription&.admin_comped?
@interval = Billing::Stripe::PriceCatalog.normalize_interval(@interval)
quote = @club.active_billing_quote
amount_cents, kind = resolve_amount(quote)
existing = @club.billing_transfer_orders.awaiting_payment.first
if existing
if existing.plan_slug == @plan_slug && existing.billing_interval == @interval && existing.amount_cents == amount_cents
BankTransferMailer.with(order: existing).instructions.deliver_now
return existing
end
existing.cancel!
end
order = nil
ApplicationRecord.transaction do
payment = @club.billing_payments.create!(
provider: "bank_transfer",
amount_cents: amount_cents,
currency: "eur",
status: "pending",
plan_slug: @plan_slug,
description: payment_description(kind, amount_cents)
)
order = @club.billing_transfer_orders.create!(
billing_club_quote: kind == "commercial_quote" ? quote : nil,
billing_payment: payment,
plan_slug: @plan_slug,
billing_interval: @interval,
amount_cents: amount_cents,
currency: "eur",
kind: kind,
status: "awaiting_payment",
reference_code: generate_reference_code,
requested_by_user: @user
)
end
BankTransferMailer.with(order: order).instructions.deliver_now
order
end
private
def resolve_amount(quote)
if quote
unless quote.matches?(@plan_slug, @interval)
raise Error, "Per questa società è attivo un prezzo concordato su #{quote.plan.name} (#{quote.price_label})."
end
return [quote.amount_cents, "commercial_quote"]
end
[Billing::Stripe::PriceCatalog.amount_cents(plan_slug: @plan_slug, interval: @interval), "list_price"]
end
def payment_description(kind, amount_cents)
label = Billing::Stripe::PriceCatalog.format_interval_price(amount_cents, @interval)
suffix = kind == "commercial_quote" ? "prezzo concordato, bonifico" : "bonifico"
"#{Plan[@plan_slug].name}#{label} (#{suffix})"
end
def generate_reference_code
8.times do
code = "MLTV-#{SecureRandom.alphanumeric(6).upcase}"
return code unless TransferOrder.exists?(reference_code: code)
end
raise Error, "Impossibile generare il riferimento del bonifico"
end
end
end