Initial commit: monorepo Match Live TV.

Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 17:45:37 +02:00
commit bba6df52c0
381 changed files with 20599 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
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)
end
end
private
def handle_checkout_completed(session)
team_id = metadata_team_id(session)
return if team_id.blank?
team = Team.find_by(id: team_id)
return unless team
sub = team.subscription || team.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, team: team, plan_slug: plan_slug)
end
end
def sync_subscription(stripe_sub, team: nil, plan_slug: nil)
team ||= Team.joins(:subscription)
.find_by(subscriptions: { stripe_subscription_id: stripe_sub.id })
team ||= Team.find_by(id: metadata_team_id(stripe_sub))
return unless team
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 = unix_time(stripe_sub.current_period_start)
period_end = unix_time(stripe_sub.current_period_end)
Billing::AssignPlan.call(
team: team,
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)
team = Team.joins(:subscription)
.find_by(subscriptions: { stripe_subscription_id: stripe_sub.id })
return unless team
Billing::AssignPlan.call(
team: team,
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 mark_past_due(invoice)
return if invoice.subscription.blank?
team = Team.joins(:subscription)
.find_by(subscriptions: { stripe_subscription_id: invoice.subscription })
return unless team&.subscription
team.subscription.update!(status: "past_due")
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_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