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:
@@ -1,18 +1,16 @@
|
||||
module Billing
|
||||
module Stripe
|
||||
# Solo per sincronizzazione interna/test mockati.
|
||||
# I cambi piano utente devono passare da CheckoutSession (Stripe Checkout hosted).
|
||||
# Non creare mai PaymentMethod con card[number] sul server (violazione PCI).
|
||||
class ChangePlan
|
||||
VALID_PLANS = CheckoutSession::VALID_PLANS.freeze
|
||||
|
||||
def self.call(club:, plan_slug:)
|
||||
new(club: club, plan_slug: plan_slug).call
|
||||
def self.call(club:, plan_slug:, interval: nil)
|
||||
new(club: club, plan_slug: plan_slug, interval: interval).call
|
||||
end
|
||||
|
||||
def initialize(club:, plan_slug:)
|
||||
def initialize(club:, plan_slug:, interval: nil)
|
||||
@club = club
|
||||
@plan_slug = plan_slug.to_s
|
||||
@interval = interval
|
||||
end
|
||||
|
||||
def call
|
||||
@@ -21,10 +19,13 @@ module Billing
|
||||
|
||||
sub = @club.subscription
|
||||
raise "Nessun abbonamento Stripe attivo" if sub.blank? || sub.stripe_subscription_id.blank?
|
||||
raise "Sei già su questo piano" if sub.plan.slug == @plan_slug
|
||||
|
||||
price_id = Plan[@plan_slug].stripe_price_id.presence || stripe_price_id_for(@plan_slug)
|
||||
raise "Price ID Stripe mancante per #{@plan_slug}" if price_id.blank?
|
||||
interval = resolve_interval(sub)
|
||||
if sub.plan.slug == @plan_slug && sub.billing_interval == interval
|
||||
raise "Sei già su questo piano e intervallo di fatturazione"
|
||||
end
|
||||
|
||||
price_id = PriceCatalog.price_id(plan_slug: @plan_slug, interval: interval)
|
||||
|
||||
stripe_sub = ::Stripe::Subscription.retrieve(sub.stripe_subscription_id)
|
||||
item_id = stripe_sub.items.data.first.id
|
||||
@@ -32,24 +33,23 @@ module Billing
|
||||
updated = ::Stripe::Subscription.update(
|
||||
stripe_sub.id,
|
||||
items: [{ id: item_id, price: price_id }],
|
||||
metadata: { club_id: @club.id, plan_slug: @plan_slug },
|
||||
metadata: { club_id: @club.id, plan_slug: @plan_slug, billing_interval: interval },
|
||||
proration_behavior: "create_prorations"
|
||||
)
|
||||
|
||||
apply_local_plan!(updated)
|
||||
apply_local_plan!(updated, interval: interval)
|
||||
updated
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stripe_price_id_for(slug)
|
||||
case slug
|
||||
when "premium_light" then MatchLiveTv.stripe_premium_light_price_id
|
||||
when "premium_full" then MatchLiveTv.stripe_premium_full_price_id
|
||||
end
|
||||
def resolve_interval(sub)
|
||||
return PriceCatalog.normalize_interval(@interval) if @interval.present?
|
||||
|
||||
sub.billing_interval.presence || PriceCatalog::DEFAULT_INTERVAL
|
||||
end
|
||||
|
||||
def apply_local_plan!(stripe_sub)
|
||||
def apply_local_plan!(stripe_sub, interval:)
|
||||
period_start, period_end = SubscriptionPeriod.times(stripe_sub)
|
||||
Billing::AssignPlan.call(
|
||||
club: @club,
|
||||
@@ -60,7 +60,8 @@ module Billing
|
||||
stripe_subscription_id: stripe_sub.id,
|
||||
current_period_start: period_start,
|
||||
current_period_end: period_end,
|
||||
cancel_at_period_end: stripe_sub.cancel_at_period_end
|
||||
cancel_at_period_end: stripe_sub.cancel_at_period_end,
|
||||
billing_interval: interval
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user