Files
MatchLiveTv/backend/app/services/billing/stripe/webhook_handler.rb
Emiliano Frascaro f4b7be0f80 Billing Stripe, link regia mobile e staff solo trasmissione.
Aggiunge fatturazione club, pagina regia condivisibile senza account, roster squadre e rimuove la modalità controller dall'app (v1.1.0).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 07:23:13 +02:00

151 lines
4.5 KiB
Ruby

module Billing
module Stripe
class WebhookHandler
def self.call(event)
new(event).call
end
def initialize(event)
@event = event
end
def call
case @event.type
when "checkout.session.completed"
handle_checkout_completed(@event.data.object)
when "customer.subscription.updated", "customer.subscription.created"
sync_subscription(@event.data.object)
when "customer.subscription.deleted"
downgrade_to_free(@event.data.object)
when "invoice.payment_failed"
mark_past_due(@event.data.object)
when "invoice.paid"
record_payment(@event.data.object)
end
end
private
def handle_checkout_completed(session)
club = resolve_club(session)
return unless club
sub = club.subscription || club.build_subscription
sub.update!(stripe_customer_id: session.customer) if session.customer.present?
if session.subscription.present?
stripe_sub = ::Stripe::Subscription.retrieve(session.subscription)
plan_slug = metadata_plan_slug(session) || "premium_light"
plan_slug = "premium_full" if plan_slug == "premium"
sync_subscription(stripe_sub, club: club, plan_slug: plan_slug)
end
end
def sync_subscription(stripe_sub, club: nil, plan_slug: nil)
club ||= Club.joins(:subscription)
.find_by(subscriptions: { stripe_subscription_id: stripe_sub.id })
club ||= resolve_club(stripe_sub)
return unless club
plan_slug ||= metadata_plan_slug(stripe_sub) || "premium_light"
plan_slug = "premium_full" if plan_slug == "premium"
plan = Plan[plan_slug]
status = map_status(stripe_sub.status)
period_start, period_end = SubscriptionPeriod.times(stripe_sub)
Billing::AssignPlan.call(
club: club,
plan_slug: plan.slug,
status: 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 downgrade_to_free(stripe_sub)
club = Club.joins(:subscription)
.find_by(subscriptions: { stripe_subscription_id: stripe_sub.id })
return unless club
Billing::AssignPlan.call(
club: club,
plan_slug: "free",
status: "active",
stripe_attrs: {
stripe_subscription_id: nil,
current_period_start: nil,
current_period_end: nil,
cancel_at_period_end: false
}
)
end
def record_payment(invoice)
Billing::Stripe::RecordPayment.call(stripe_invoice: invoice)
end
def mark_past_due(invoice)
return if invoice.subscription.blank?
club = Club.joins(:subscription)
.find_by(subscriptions: { stripe_subscription_id: invoice.subscription })
return unless club&.subscription
club.subscription.update!(status: "past_due")
end
def resolve_club(obj)
club_id = metadata_club_id(obj)
return Club.find_by(id: club_id) if club_id.present?
team_id = metadata_team_id(obj)
return Team.find_by(id: team_id)&.club if team_id.present?
nil
end
def unix_time(value)
return nil if value.blank?
Time.zone.at(value.to_i)
end
def metadata_plan_slug(obj)
meta = obj.metadata
return nil if meta.blank?
meta["plan_slug"] || meta[:plan_slug] || (meta.respond_to?(:plan_slug) ? meta.plan_slug : nil)
end
def metadata_club_id(obj)
meta = obj.metadata
return nil if meta.blank?
meta["club_id"] || meta[:club_id] || (meta.respond_to?(:club_id) ? meta.club_id : nil)
end
def metadata_team_id(obj)
meta = obj.metadata
return nil if meta.blank?
meta["team_id"] || meta[:team_id] || (meta.respond_to?(:team_id) ? meta.team_id : nil)
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