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:
129
backend/app/services/billing/payment_description.rb
Normal file
129
backend/app/services/billing/payment_description.rb
Normal file
@@ -0,0 +1,129 @@
|
||||
module Billing
|
||||
# Etichette leggibili per lo storico pagamenti (mai testo grezzo Stripe / webhook).
|
||||
class PaymentDescription
|
||||
UGLY = /\A(webhook\s*test|test|x|—|-)\z/i
|
||||
|
||||
AMOUNT_HINTS = {
|
||||
500 => %w[premium_light monthly],
|
||||
4000 => %w[premium_light yearly],
|
||||
2000 => %w[premium_full monthly],
|
||||
20_000 => %w[premium_full yearly]
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
def for_stripe_invoice(invoice)
|
||||
plan_slug, interval = extract_plan_and_interval(invoice)
|
||||
build(plan_slug: plan_slug, interval: interval, amount_cents: invoice.amount_paid)
|
||||
end
|
||||
|
||||
def for_payment(payment)
|
||||
stored = payment.description.to_s.strip
|
||||
return stored if stored.present? && !ugly?(stored)
|
||||
|
||||
build(
|
||||
plan_slug: payment.plan_slug,
|
||||
interval: nil,
|
||||
amount_cents: payment.amount_cents
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build(plan_slug:, interval:, amount_cents:)
|
||||
plan_slug, interval = resolve_plan_interval(plan_slug, interval, amount_cents)
|
||||
return plan_label(plan_slug, interval) if plan_slug.present?
|
||||
|
||||
if amount_cents.present? && amount_cents.to_i.positive?
|
||||
euros = format("%.2f", amount_cents.to_i / 100.0)
|
||||
return "Pagamento Match Live TV — #{euros} €"
|
||||
end
|
||||
|
||||
"Abbonamento Match Live TV"
|
||||
end
|
||||
|
||||
def resolve_plan_interval(plan_slug, interval, amount_cents)
|
||||
plan_slug = plan_slug.presence
|
||||
interval = interval.presence
|
||||
|
||||
if plan_slug.blank? && amount_cents.present?
|
||||
hint = AMOUNT_HINTS[amount_cents.to_i]
|
||||
plan_slug, interval = hint if hint
|
||||
end
|
||||
|
||||
if plan_slug.present? && interval.blank? && amount_cents.present?
|
||||
hint = AMOUNT_HINTS[amount_cents.to_i]
|
||||
interval = hint&.last if hint&.first == plan_slug
|
||||
end
|
||||
|
||||
interval ||= Billing::Stripe::PriceCatalog::DEFAULT_INTERVAL if plan_slug.present?
|
||||
[plan_slug, interval]
|
||||
end
|
||||
|
||||
def plan_label(plan_slug, interval)
|
||||
plan = Plan[plan_slug]
|
||||
price = Billing::Stripe::PriceCatalog.label(plan_slug: plan_slug, interval: interval)
|
||||
"#{plan.name} — #{price}"
|
||||
rescue KeyError
|
||||
plan_slug.to_s.humanize
|
||||
end
|
||||
|
||||
def extract_plan_and_interval(invoice)
|
||||
meta = merged_metadata(invoice)
|
||||
plan_slug = meta["plan_slug"].presence
|
||||
interval = normalize_interval(meta["billing_interval"])
|
||||
|
||||
line = invoice.lines&.data&.first
|
||||
if line
|
||||
line_meta = line_metadata(line)
|
||||
plan_slug ||= line_meta["plan_slug"]
|
||||
interval ||= normalize_interval(line_meta["billing_interval"])
|
||||
|
||||
price_id = line_price_id(line)
|
||||
from_price = Billing::Stripe::PriceCatalog.infer_from_price_id(price_id)
|
||||
plan_slug ||= from_price&.first
|
||||
interval ||= from_price&.last
|
||||
end
|
||||
|
||||
plan_slug = normalize_plan_slug(plan_slug)
|
||||
[plan_slug, interval]
|
||||
end
|
||||
|
||||
def merged_metadata(invoice)
|
||||
Billing::Stripe::InvoiceMetadata.merged(invoice)
|
||||
end
|
||||
|
||||
def line_metadata(line)
|
||||
meta = Billing::Stripe::InvoicePaymentRefs.safe_get(line, :metadata)
|
||||
meta.to_h.stringify_keys
|
||||
rescue NoMethodError
|
||||
{}
|
||||
end
|
||||
|
||||
def line_price_id(line)
|
||||
pricing = Billing::Stripe::InvoicePaymentRefs.safe_get(line, :pricing)
|
||||
details = Billing::Stripe::InvoicePaymentRefs.safe_get(pricing, :price_details)
|
||||
Billing::Stripe::InvoicePaymentRefs.safe_get(details, :price)
|
||||
end
|
||||
|
||||
def normalize_interval(value)
|
||||
return nil if value.blank?
|
||||
|
||||
Billing::Stripe::PriceCatalog.normalize_interval(value)
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end
|
||||
|
||||
def normalize_plan_slug(slug)
|
||||
return nil if slug.blank?
|
||||
|
||||
slug = slug.to_s
|
||||
slug = "premium_full" if slug == "premium"
|
||||
slug.presence_in(%w[premium_light premium_full])
|
||||
end
|
||||
|
||||
def ugly?(text)
|
||||
UGLY.match?(text.to_s.strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user