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
This commit is contained in:
@@ -317,4 +317,142 @@ class EventTest < ActiveSupport::TestCase
|
||||
# Check that ticket types were NOT duplicated
|
||||
assert_equal 0, duplicated_event.ticket_types.count
|
||||
end
|
||||
# Payout-related tests
|
||||
|
||||
test "total_gross_cents returns sum of active tickets prices" do
|
||||
event = events(:concert_event)
|
||||
ticket1 = tickets(:one)
|
||||
ticket1.status = "active"
|
||||
ticket1.price_cents = 1000
|
||||
ticket1.save!
|
||||
|
||||
ticket2 = Ticket.create!(order: orders(:one), ticket_type: ticket_types(:one), qr_code: "qr2", price_cents: 2000, status: "active", first_name: "Test", last_name: "User")
|
||||
ticket2.event = event
|
||||
ticket2.save!
|
||||
|
||||
assert_equal 3000, event.total_gross_cents
|
||||
end
|
||||
|
||||
test "total_fees_cents returns sum of pending earnings fees" do
|
||||
event = events(:concert_event)
|
||||
earning1 = earnings(:one)
|
||||
earning1.status = "pending"
|
||||
earning1.fee_cents = 100
|
||||
earning1.save!
|
||||
|
||||
earning2 = Earning.create!(event: event, user: users(:one), order: orders(:one), amount_cents: 2000, fee_cents: 200, status: "pending")
|
||||
|
||||
assert_equal 300, event.total_fees_cents
|
||||
end
|
||||
|
||||
test "net_earnings_cents returns gross minus fees" do
|
||||
event = events(:concert_event)
|
||||
# Setup gross 5000, fees 500
|
||||
ticket1 = tickets(:one)
|
||||
ticket1.status = "active"
|
||||
ticket1.price_cents = 2500
|
||||
ticket1.save!
|
||||
|
||||
ticket2 = Ticket.create!(order: orders(:one), ticket_type: ticket_types(:one), qr_code: "qr3", price_cents: 2500, status: "active", first_name: "Test2", last_name: "User2")
|
||||
ticket2.event = event
|
||||
ticket2.save!
|
||||
|
||||
earning1 = earnings(:one)
|
||||
earning1.status = "pending"
|
||||
earning1.fee_cents = 250
|
||||
earning1.save!
|
||||
|
||||
earning2 = Earning.create!(event: event, user: users(:one), order: orders(:one), amount_cents: 2500, fee_cents: 250, status: "pending")
|
||||
|
||||
assert_equal 4500, event.net_earnings_cents
|
||||
end
|
||||
|
||||
test "can_request_payout? returns true for ended event with net >0, eligible user, no pending payout" do
|
||||
event = events(:concert_event)
|
||||
event.update!(end_time: 1.day.ago) # ended
|
||||
# Setup net >0
|
||||
ticket = Ticket.create!(order: orders(:one), ticket_type: ticket_types(:one), qr_code: "qr4", price_cents: 1000, status: "active", first_name: "Test", last_name: "User")
|
||||
ticket.event = event
|
||||
ticket.save!
|
||||
|
||||
earning = Earning.create!(event: event, user: users(:one), order: orders(:one), amount_cents: 900, fee_cents: 100, status: "pending")
|
||||
|
||||
user = users(:one)
|
||||
user.update!(is_professionnal: true) # eligible
|
||||
|
||||
# No pending payout
|
||||
assert event.can_request_payout?(user)
|
||||
end
|
||||
|
||||
test "can_request_payout? returns false for not ended event" do
|
||||
event = events(:concert_event)
|
||||
event.update!(end_time: 1.day.from_now) # not ended
|
||||
user = users(:one)
|
||||
user.update!(is_professionnal: true)
|
||||
|
||||
assert_not event.can_request_payout?(user)
|
||||
end
|
||||
|
||||
test "can_request_payout? returns false if net <=0" do
|
||||
event = events(:concert_event)
|
||||
event.update!(end_time: 1.day.ago)
|
||||
user = users(:one)
|
||||
user.update!(is_professionnal: true)
|
||||
|
||||
assert_not event.can_request_payout?(user)
|
||||
end
|
||||
|
||||
test "can_request_payout? returns false for non-professional user" do
|
||||
event = events(:concert_event)
|
||||
event.update!(end_time: 1.day.ago)
|
||||
# Setup net >0
|
||||
ticket = Ticket.create!(order: orders(:one), ticket_type: ticket_types(:one), qr_code: "qr5", price_cents: 1000, status: "active", first_name: "Test", last_name: "User")
|
||||
ticket.event = event
|
||||
ticket.save!
|
||||
|
||||
earning = Earning.create!(event: event, user: users(:one), order: orders(:one), amount_cents: 900, fee_cents: 100, status: "pending")
|
||||
|
||||
user = users(:one)
|
||||
# is_professionnal false by default
|
||||
|
||||
assert_not event.can_request_payout?(user)
|
||||
end
|
||||
|
||||
test "can_request_payout? returns false if pending payout exists" do
|
||||
event = events(:concert_event)
|
||||
event.update!(end_time: 1.day.ago)
|
||||
# Setup net >0
|
||||
ticket = Ticket.create!(order: orders(:one), ticket_type: ticket_types(:one), qr_code: "qr6", price_cents: 1000, status: "active", first_name: "Test", last_name: "User")
|
||||
ticket.event = event
|
||||
ticket.save!
|
||||
|
||||
earning = Earning.create!(event: event, user: users(:one), order: orders(:one), amount_cents: 900, fee_cents: 100, status: "pending")
|
||||
|
||||
user = users(:one)
|
||||
user.update!(is_professionnal: true)
|
||||
|
||||
Payout.create!(user: user, event: event, amount_cents: 800, fee_cents: 100, status: :pending)
|
||||
|
||||
assert_not event.can_request_payout?(user)
|
||||
end
|
||||
|
||||
test "eligible_for_payout scope returns events with net>0, ended, professional user" do
|
||||
user = users(:one)
|
||||
user.update!(is_professionnal: true)
|
||||
|
||||
eligible = Event.create!(name: "Eligible", slug: "eligible", description: "desc", venue_name: "v", venue_address: "a", latitude: 48.0, longitude: 2.0, start_time: 1.day.ago, end_time: 2.days.ago, user: user, state: :published)
|
||||
# Setup net >0
|
||||
ticket = Ticket.create!(order: orders(:one), ticket_type: ticket_types(:one), qr_code: "qr7", price_cents: 1000, status: "active", first_name: "Test", last_name: "User")
|
||||
ticket.event = eligible
|
||||
ticket.save!
|
||||
|
||||
earning = Earning.create!(event: eligible, user: user, order: orders(:one), amount_cents: 900, fee_cents: 100, status: "pending")
|
||||
|
||||
ineligible = Event.create!(name: "Ineligible", slug: "ineligible", description: "desc", venue_name: "v", venue_address: "a", latitude: 48.0, longitude: 2.0, start_time: 1.day.from_now, end_time: 2.days.from_now, user: user, state: :published)
|
||||
# net =0
|
||||
|
||||
eligible_events = Event.eligible_for_payout
|
||||
assert_includes eligible_events, eligible
|
||||
assert_not_includes eligible_events, ineligible
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user