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 end def initialize(club:, plan_slug:) @club = club @plan_slug = plan_slug.to_s end def call raise "Stripe non configurato" unless MatchLiveTv.stripe_enabled? raise "Piano non valido" unless VALID_PLANS.include?(@plan_slug) 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? stripe_sub = ::Stripe::Subscription.retrieve(sub.stripe_subscription_id) item_id = stripe_sub.items.data.first.id updated = ::Stripe::Subscription.update( stripe_sub.id, items: [{ id: item_id, price: price_id }], metadata: { club_id: @club.id, plan_slug: @plan_slug }, proration_behavior: "create_prorations" ) apply_local_plan!(updated) 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 end def apply_local_plan!(stripe_sub) period_start, period_end = SubscriptionPeriod.times(stripe_sub) Billing::AssignPlan.call( club: @club, plan_slug: @plan_slug, status: map_status(stripe_sub.status), stripe_attrs: { stripe_customer_id: stripe_sub.customer, 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 } ) end def map_status(stripe_status) case stripe_status when "trialing" then "trialing" when "active" then "active" when "past_due", "unpaid" then "past_due" when "canceled", "incomplete_expired" then "canceled" else "incomplete" end end end end end