Files
aperonight/app/models/payout.rb
kbe 3c1e17c2af feat(payouts): implement promoter earnings viewing, request flow, and admin Stripe processing with webhooks
Add model methods for accurate net calculations (€0.50 + 1.5% fees), eligibility, refund handling
Update promoter/payouts controller for index (pending events), create (eligibility checks)
Integrate admin processing via Stripe::Transfer, webhook for status sync
Enhance views: index pending cards, events/show preview/form
Add comprehensive tests (models, controllers, service, integration); run migrations
2025-09-17 02:07:52 +02:00

96 lines
2.6 KiB
Ruby

class Payout < ApplicationRecord
# === Relations ===
belongs_to :user
belongs_to :event
# === Enums ===
enum :status, {
pending: 0, # Payout requested but not processed
processing: 1, # Payout being processed
completed: 2, # Payout successfully completed
failed: 3 # Payout failed
}, default: :pending
# === Validations ===
validates :amount_cents, presence: true, numericality: { greater_than: 0 }
validates :fee_cents, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :status, presence: true
validates :total_orders_count, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :refunded_orders_count, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :stripe_payout_id, allow_blank: true, uniqueness: true
validate :unique_pending_event_id, if: :pending?
validate :net_earnings_greater_than_zero, if: :pending?
def net_earnings_greater_than_zero
if event.net_earnings_cents <= 0
errors.add(:base, "net earnings must be greater than 0")
end
end
validate :net_earnings_greater_than_zero, if: :pending?
def net_earnings_greater_than_zero
if event.net_earnings_cents <= 0
errors.add(:base, "net earnings must be greater than 0")
end
end
def unique_pending_event_id
if Payout.pending.where(event_id: event_id).where.not(id: id).exists?
errors.add(:base, "only one pending payout allowed per event")
end
end
# === Scopes ===
scope :completed, -> { where(status: :completed) }
scope :pending, -> { where(status: :pending) }
scope :processing, -> { where(status: :processing) }
# === Callbacks ===
after_create :calculate_refunded_orders_count
# === Instance Methods ===
# Amount in euros (formatted)
def amount_euros
amount_cents / 100.0
end
# Fee in euros (formatted)
def fee_euros
fee_cents / 100.0
end
# Net amount after fees
def net_amount_cents
amount_cents - fee_cents
end
# Net amount in euros
def net_amount_euros
net_amount_cents / 100.0
end
# Check if payout can be processed
def can_process?
pending? && amount_cents > 0
end
# Process the payout through Stripe
def process_payout!
service = PayoutService.new(self)
service.process!
end
public
# === Instance Methods ===
def calculate_refunded_orders_count
refunded_order_ids = event.tickets.where(status: "refunded").select(:order_id).distinct.pluck(:order_id)
paid_statuses = %w[paid completed]
count = event.orders.where(status: paid_statuses).where(id: refunded_order_ids).count
update_column(:refunded_orders_count, count)
end
end