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:
kbe
2025-09-17 02:07:52 +02:00
parent 47f4f50e5b
commit 3c1e17c2af
31 changed files with 1096 additions and 148 deletions

View File

@@ -53,6 +53,65 @@
</div>
<% end %>
<!-- Pending Earnings Section -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-8">
<h2 class="text-lg font-medium text-gray-900 mb-4">Pending Earnings</h2>
<% if @total_pending_net && @total_pending_net > 0 %>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div class="flex items-center">
<div class="p-2 bg-yellow-100 rounded-lg">
<i data-lucide="dollar-sign" class="w-6 h-6 text-yellow-600"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Total Pending Net</p>
<p class="text-2xl font-bold text-gray-900">
<%= number_to_currency(@total_pending_net / 100.0, unit: '€', separator: ',', delimiter: '.') %>
</p>
</div>
</div>
</div>
</div>
<% end %>
<% if @eligible_events.present? && @eligible_events.any? %>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<% @eligible_events.limit(5).each do |event| %>
<div class="bg-white p-6 rounded-lg shadow border border-gray-200">
<div class="flex items-center mb-3">
<div class="flex-shrink-0 h-10 w-10 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg flex items-center justify-center">
<i data-lucide="calendar" class="h-5 w-5 text-white"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-bold text-gray-900"><%= event.name %></h3>
<p class="text-sm text-gray-500"><%= event.start_time.strftime("%d %b %Y") %></p>
</div>
</div>
<div class="space-y-2 text-sm">
<p><span class="font-medium">Gross:</span> <%= number_to_currency(event.total_earnings_cents / 100.0, unit: '€', separator: ',', delimiter: '.') %></p>
<p><span class="font-medium">Net:</span> <%= number_to_currency(event.net_earnings_cents / 100.0, unit: '€', separator: ',', delimiter: '.') %></p>
</div>
<%= link_to "Request Payout", promoter_event_path(event),
class: "mt-4 w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
</div>
<% end %>
</div>
<% if @eligible_events.size > 5 %>
<div class="text-center mt-4">
<%= link_to "View All Eligible Events", promoter_events_path, class: "text-indigo-600 hover:text-indigo-500 text-sm font-medium" %>
</div>
<% end %>
<% else %>
<div class="text-center py-12">
<i data-lucide="inbox" class="mx-auto h-12 w-12 text-gray-400 mb-4"></i>
<h3 class="text-lg font-medium text-gray-900 mb-2">No pending earnings</h3>
<p class="text-gray-500">Check your events to see if any are eligible for payout requests.</p>
<%= link_to "View My Events", promoter_events_path, class: "mt-4 inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50" %>
</div>
<% end %>
</div>
<!-- Payouts Table -->
<% if @payouts.any? %>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
@@ -139,4 +198,4 @@
<%= link_to "View My Events", promoter_events_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
</div>
<% end %>
</div>
</div>