feat: Add manual payout system for countries without Stripe Global Payouts
This commit is contained in:
@@ -25,12 +25,28 @@ class Admin::PayoutsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Mark a payout as manually processed (for SEPA transfers, etc.)
|
||||||
|
def mark_as_manually_processed
|
||||||
|
@payout = Payout.find(params[:id])
|
||||||
|
|
||||||
|
if @payout.pending? || @payout.processing?
|
||||||
|
begin
|
||||||
|
@payout.mark_as_manually_processed!
|
||||||
|
redirect_to admin_payouts_path, notice: "Payout marked as manually processed. Please complete the bank transfer."
|
||||||
|
rescue => e
|
||||||
|
redirect_to admin_payouts_path, alert: "Failed to mark payout as manually processed: #{e.message}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
redirect_to admin_payouts_path, alert: "Cannot mark this payout as manually processed."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_admin!
|
def ensure_admin!
|
||||||
# For now, we'll just check if the user has a stripe account
|
# For now, we'll just check if the user is a professional user
|
||||||
# In a real app, you'd have an admin role check
|
# In a real app, you'd have an admin role check
|
||||||
unless current_user.has_stripe_account?
|
unless current_user.promoter?
|
||||||
redirect_to dashboard_path, alert: "Access denied."
|
redirect_to dashboard_path, alert: "Access denied."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -82,6 +82,30 @@ class Payout < ApplicationRecord
|
|||||||
service = PayoutService.new(self)
|
service = PayoutService.new(self)
|
||||||
service.process!
|
service.process!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Mark payout as manually processed (for countries where Stripe payouts are not available)
|
||||||
|
def mark_as_manually_processed!
|
||||||
|
return unless pending? || processing?
|
||||||
|
|
||||||
|
update!(
|
||||||
|
status: :completed,
|
||||||
|
stripe_payout_id: "MANUAL_#{SecureRandom.hex(10)}" # Generate a unique ID for manual payouts
|
||||||
|
)
|
||||||
|
|
||||||
|
update_earnings_status
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if this is a manual payout (not processed through Stripe)
|
||||||
|
def manual_payout?
|
||||||
|
stripe_payout_id.present? && stripe_payout_id.start_with?("MANUAL_")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def update_earnings_status
|
||||||
|
event.earnings.where(status: 0).update_all(status: 1) # pending to paid
|
||||||
|
end
|
||||||
|
|
||||||
public
|
public
|
||||||
|
|
||||||
# === Instance Methods ===
|
# === Instance Methods ===
|
||||||
|
|||||||
@@ -6,6 +6,39 @@ class PayoutService
|
|||||||
def process!
|
def process!
|
||||||
return unless @payout.can_process?
|
return unless @payout.can_process?
|
||||||
|
|
||||||
|
# Check if user is in France or doesn't have a Stripe account (manual processing)
|
||||||
|
if should_process_manually?
|
||||||
|
process_manually!
|
||||||
|
else
|
||||||
|
process_with_stripe!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def should_process_manually?
|
||||||
|
# For now, we'll assume manual processing for all users
|
||||||
|
# In a real implementation, this could check the user's country
|
||||||
|
!@payout.user.has_stripe_account?
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_manually!
|
||||||
|
@payout.update!(status: :processing)
|
||||||
|
|
||||||
|
begin
|
||||||
|
# For manual processing, we just mark it as completed
|
||||||
|
# In a real implementation, this would trigger notifications to admin
|
||||||
|
@payout.mark_as_manually_processed!
|
||||||
|
|
||||||
|
Rails.logger.info "Manual payout processed for payout #{@payout.id} for event #{@payout.event.name}"
|
||||||
|
rescue => e
|
||||||
|
@payout.update!(status: :failed)
|
||||||
|
Rails.logger.error "Manual payout failed for payout #{@payout.id}: #{e.message}"
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_with_stripe!
|
||||||
@payout.update!(status: :processing)
|
@payout.update!(status: :processing)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@@ -31,8 +64,6 @@ class PayoutService
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def update_earnings_status
|
def update_earnings_status
|
||||||
@payout.event.earnings.where(status: 0).update_all(status: 1) # pending to paid
|
@payout.event.earnings.where(status: 0).update_all(status: 1) # pending to paid
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -57,6 +57,11 @@
|
|||||||
<%= button_to "Process", admin_payout_path(payout), method: :post,
|
<%= button_to "Process", admin_payout_path(payout), method: :post,
|
||||||
class: "text-indigo-600 hover:text-indigo-900 bg-indigo-100 hover:bg-indigo-200 px-3 py-1 rounded" %>
|
class: "text-indigo-600 hover:text-indigo-900 bg-indigo-100 hover:bg-indigo-200 px-3 py-1 rounded" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if payout.pending? || payout.processing? %>
|
||||||
|
<%= button_to "Mark as Manually Processed", mark_as_manually_processed_admin_payout_path(payout), method: :post,
|
||||||
|
class: "text-green-600 hover:text-green-900 bg-green-100 hover:bg-green-200 px-3 py-1 rounded ml-2",
|
||||||
|
data: { confirm: "Are you sure you want to mark this payout as manually processed? This will notify the promoter that the bank transfer is being processed." } %>
|
||||||
|
<% end %>
|
||||||
<%= link_to "View", promoter_payout_path(payout), class: "text-indigo-600 hover:text-indigo-900 ml-2" %>
|
<%= link_to "View", promoter_payout_path(payout), class: "text-indigo-600 hover:text-indigo-900 ml-2" %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,2 +1,122 @@
|
|||||||
<h1>Admin::Payouts#show</h1>
|
<div class="container mx-auto px-4 py-8">
|
||||||
<p>Find me in app/views/admin/payouts/show.html.erb</p>
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900">Payout Details</h1>
|
||||||
|
<%= link_to "Back to Payouts", admin_payouts_path, class: "text-indigo-600 hover:text-indigo-900" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||||
|
<div class="px-6 py-4 border-b border-gray-200">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-800">Payout #<%= @payout.id %></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Event Information</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Event Name</p>
|
||||||
|
<p class="text-sm text-gray-900"><%= @payout.event.name %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Event Date</p>
|
||||||
|
<p class="text-sm text-gray-900"><%= @payout.event.start_time.strftime("%B %d, %Y") %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Promoter Information</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Name</p>
|
||||||
|
<p class="text-sm text-gray-900"><%= @payout.user.name.presence || @payout.user.email %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Email</p>
|
||||||
|
<p class="text-sm text-gray-900"><%= @payout.user.email %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Financial Details</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Gross Amount</p>
|
||||||
|
<p class="text-sm text-gray-900">€<%= @payout.amount_euros %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Platform Fees</p>
|
||||||
|
<p class="text-sm text-gray-900">€<%= @payout.fee_euros %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Net Amount</p>
|
||||||
|
<p class="text-sm text-gray-900">€<%= @payout.net_amount_euros %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Payout Information</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Status</p>
|
||||||
|
<p class="text-sm text-gray-900">
|
||||||
|
<% case @payout.status %>
|
||||||
|
<% when 'pending' %>
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
<% when 'processing' %>
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||||
|
Processing
|
||||||
|
</span>
|
||||||
|
<% when 'completed' %>
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||||
|
Completed
|
||||||
|
</span>
|
||||||
|
<% when 'failed' %>
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
||||||
|
Failed
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Created At</p>
|
||||||
|
<p class="text-sm text-gray-900"><%= @payout.created_at.strftime("%B %d, %Y at %H:%M") %></p>
|
||||||
|
</div>
|
||||||
|
<% if @payout.stripe_payout_id.present? %>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-500">Payout ID</p>
|
||||||
|
<p class="text-sm text-gray-900">
|
||||||
|
<% if @payout.manual_payout? %>
|
||||||
|
Manual Transfer - <%= @payout.stripe_payout_id %>
|
||||||
|
<% else %>
|
||||||
|
<%= @payout.stripe_payout_id %>
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 flex space-x-4">
|
||||||
|
<% if @payout.can_process? %>
|
||||||
|
<%= button_to "Process Payout", admin_payout_path(@payout), method: :post,
|
||||||
|
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" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if @payout.pending? || @payout.processing? %>
|
||||||
|
<%= button_to "Mark as Manually Processed", mark_as_manually_processed_admin_payout_path(@payout), method: :post,
|
||||||
|
class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500",
|
||||||
|
data: { confirm: "Are you sure you want to mark this payout as manually processed? This will notify the promoter that the bank transfer is being processed." } %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= link_to "View as Promoter", promoter_payout_path(@payout), class: "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 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -159,7 +159,11 @@
|
|||||||
<% when 'completed' %>
|
<% when 'completed' %>
|
||||||
<span class="payout-status-badge completed">
|
<span class="payout-status-badge completed">
|
||||||
<i data-lucide="check-circle" class="w-3 h-3 mr-1"></i>
|
<i data-lucide="check-circle" class="w-3 h-3 mr-1"></i>
|
||||||
Completed
|
<% if payout.manual_payout? %>
|
||||||
|
Manually Processed
|
||||||
|
<% else %>
|
||||||
|
Completed
|
||||||
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<% when 'failed' %>
|
<% when 'failed' %>
|
||||||
<span class="payout-status-badge failed">
|
<span class="payout-status-badge failed">
|
||||||
|
|||||||
@@ -162,8 +162,41 @@
|
|||||||
|
|
||||||
<% if @payout.stripe_payout_id.present? %>
|
<% if @payout.stripe_payout_id.present? %>
|
||||||
<div class="px-4 py-5 sm:px-6 payout-detail-item">
|
<div class="px-4 py-5 sm:px-6 payout-detail-item">
|
||||||
<dt class="payout-detail-label">Stripe Payout ID</dt>
|
<dt class="payout-detail-label">
|
||||||
<dd class="payout-detail-value font-mono text-xs break-all"><%= @payout.stripe_payout_id %></dd>
|
<% if @payout.manual_payout? %>
|
||||||
|
Manual Payout ID
|
||||||
|
<% else %>
|
||||||
|
Stripe Payout ID
|
||||||
|
<% end %>
|
||||||
|
</dt>
|
||||||
|
<dd class="payout-detail-value font-mono text-xs break-all">
|
||||||
|
<% if @payout.manual_payout? %>
|
||||||
|
Manual Transfer - <%= @payout.stripe_payout_id %>
|
||||||
|
<% else %>
|
||||||
|
<%= @payout.stripe_payout_id %>
|
||||||
|
<% end %>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if @payout.manual_payout? && @payout.completed? %>
|
||||||
|
<div class="px-4 py-5 sm:px-6 payout-detail-item">
|
||||||
|
<dt class="payout-detail-label">Manual Processing Note</dt>
|
||||||
|
<dd class="payout-detail-value">
|
||||||
|
<div class="bg-blue-50 border border-blue-200 rounded-md p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i data-lucide="info" class="h-5 w-5 text-blue-400"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-blue-800">Bank Transfer Initiated</h3>
|
||||||
|
<div class="mt-2 text-sm text-blue-700">
|
||||||
|
<p>Your payout is being processed via bank transfer. Please allow 1-3 business days for the funds to appear in your account.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Rails.application.routes.draw do
|
|||||||
resources :payouts, only: [ :index, :show ] do
|
resources :payouts, only: [ :index, :show ] do
|
||||||
member do
|
member do
|
||||||
post :process
|
post :process
|
||||||
|
post :mark_as_manually_processed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,6 +41,26 @@ class Admin::PayoutsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal :failed, @payout.reload.status
|
assert_equal :failed, @payout.reload.status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "mark_as_manually_processed updates payout status" do
|
||||||
|
sign_in @admin_user
|
||||||
|
@payout.update(status: :pending)
|
||||||
|
|
||||||
|
post mark_as_manually_processed_admin_payout_url(@payout)
|
||||||
|
assert_redirected_to admin_payouts_path
|
||||||
|
assert_flash :notice, /marked as manually processed/
|
||||||
|
assert @payout.reload.completed?
|
||||||
|
assert @payout.manual_payout?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "mark_as_manually_processed fails for completed payout" do
|
||||||
|
sign_in @admin_user
|
||||||
|
@payout.update(status: :completed)
|
||||||
|
|
||||||
|
post mark_as_manually_processed_admin_payout_url(@payout)
|
||||||
|
assert_redirected_to admin_payouts_path
|
||||||
|
assert_flash :alert, /Cannot mark this payout as manually processed/
|
||||||
|
end
|
||||||
|
|
||||||
test "requires admin authentication" do
|
test "requires admin authentication" do
|
||||||
patch admin_payout_url(@payout)
|
patch admin_payout_url(@payout)
|
||||||
assert_redirected_to new_user_session_path
|
assert_redirected_to new_user_session_path
|
||||||
|
|||||||
@@ -135,6 +135,17 @@ class Promoter::PayoutsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_flash :alert, /Event not eligible for payout/
|
assert_flash :alert, /Event not eligible for payout/
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "show renders manual payout details correctly" do
|
||||||
|
sign_in @user
|
||||||
|
@user.update(is_professionnal: true)
|
||||||
|
payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100, status: :completed, stripe_payout_id: "MANUAL_abc123")
|
||||||
|
|
||||||
|
get promoter_payout_url(payout)
|
||||||
|
assert_response :success
|
||||||
|
assert_match "Manual Payout ID", @response.body
|
||||||
|
assert_match "Manual Transfer", @response.body
|
||||||
|
end
|
||||||
|
|
||||||
# Create failure: validation errors
|
# Create failure: validation errors
|
||||||
test "create payout fails with validation errors" do
|
test "create payout fails with validation errors" do
|
||||||
sign_in @user
|
sign_in @user
|
||||||
|
|||||||
@@ -2,9 +2,23 @@ require "test_helper"
|
|||||||
|
|
||||||
class PayoutTest < ActiveSupport::TestCase
|
class PayoutTest < ActiveSupport::TestCase
|
||||||
setup do
|
setup do
|
||||||
@payout = payouts(:one)
|
@user = User.create!(email: "test@example.com", password: "password123", is_professionnal: true)
|
||||||
@user = users(:one)
|
@event = Event.create!(
|
||||||
@event = events(:concert_event)
|
user: @user,
|
||||||
|
name: "Test Event",
|
||||||
|
slug: "test-event",
|
||||||
|
description: "Test event description",
|
||||||
|
venue_name: "Test Venue",
|
||||||
|
venue_address: "Test Address",
|
||||||
|
latitude: 48.8566,
|
||||||
|
longitude: 2.3522,
|
||||||
|
start_time: 1.day.ago,
|
||||||
|
end_time: 1.hour.ago,
|
||||||
|
state: :published
|
||||||
|
)
|
||||||
|
# Create some earnings for the event
|
||||||
|
Earning.create!(event: @event, user: @user, order: Order.create!(user: @user, event: @event, status: :paid, total_amount_cents: 1000), amount_cents: 2000, fee_cents: 200, status: :pending)
|
||||||
|
@payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should be valid" do
|
test "should be valid" do
|
||||||
@@ -36,14 +50,24 @@ class PayoutTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "validations: net earnings must be greater than 0" do
|
test "validations: net earnings must be greater than 0" do
|
||||||
# Assuming event.net_earnings_cents is a method that calculates >0
|
# Create an event with no earnings (net earnings = 0)
|
||||||
@event.earnings.create!(user: @user, order: orders(:one), amount_cents: 0, fee_cents: 0, status: :pending)
|
event_without_earnings = Event.create!(
|
||||||
payout = Payout.new(user: @user, event: @event, amount_cents: 1000, fee_cents: 100)
|
user: @user,
|
||||||
|
name: "Test Event",
|
||||||
|
slug: "test-event-2",
|
||||||
|
description: "Test event description",
|
||||||
|
venue_name: "Test Venue",
|
||||||
|
venue_address: "Test Address",
|
||||||
|
latitude: 48.8566,
|
||||||
|
longitude: 2.3522,
|
||||||
|
start_time: 1.day.ago,
|
||||||
|
end_time: 1.hour.ago,
|
||||||
|
state: :published
|
||||||
|
)
|
||||||
|
|
||||||
|
payout = Payout.new(user: @user, event: event_without_earnings, amount_cents: 1000, fee_cents: 100)
|
||||||
assert_not payout.valid?
|
assert_not payout.valid?
|
||||||
assert_includes payout.errors[:base], "net earnings must be greater than 0" # Custom validation message
|
assert_includes payout.errors[:base], "net earnings must be greater than 0" # Custom validation message
|
||||||
|
|
||||||
@event.earnings.first.update(amount_cents: 2000)
|
|
||||||
assert payout.valid?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "validations: only one pending payout per event" do
|
test "validations: only one pending payout per event" do
|
||||||
@@ -62,9 +86,14 @@ class PayoutTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "after_create callback sets refunded_orders_count" do
|
test "after_create callback sets refunded_orders_count" do
|
||||||
refund_count = @event.orders.refunded.count # Assuming orders have refunded status
|
# Create some refunded tickets to test the callback
|
||||||
|
order = Order.create!(user: @user, event: @event, status: :paid, total_amount_cents: 1000)
|
||||||
|
ticket_type = TicketType.create!(event: @event, name: "General Admission", price_cents: 1000, quantity: 10)
|
||||||
|
ticket = Ticket.create!(order: order, ticket_type: ticket_type, price_cents: 1000, status: :refunded)
|
||||||
|
|
||||||
payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100)
|
payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100)
|
||||||
assert_equal refund_count, payout.refunded_orders_count
|
# The refunded_orders_count should be set by the callback
|
||||||
|
assert_equal 1, payout.refunded_orders_count
|
||||||
end
|
end
|
||||||
|
|
||||||
test "associations: belongs to user" do
|
test "associations: belongs to user" do
|
||||||
@@ -98,12 +127,29 @@ class PayoutTest < ActiveSupport::TestCase
|
|||||||
assert_not_includes Payout.pending, completed
|
assert_not_includes Payout.pending, completed
|
||||||
end
|
end
|
||||||
|
|
||||||
test "scope: eligible_for_payout" do
|
test "manual_payout? returns true for manual payouts" do
|
||||||
# Assuming this scope exists or test if needed
|
payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100,
|
||||||
eligible_event = events(:another_event) # Setup with net >0, ended, etc.
|
stripe_payout_id: "MANUAL_abc123")
|
||||||
ineligible = events(:ineligible)
|
assert payout.manual_payout?
|
||||||
|
|
||||||
eligible_payouts = Payout.eligible_for_payout
|
|
||||||
assert_includes eligible_payouts, eligible_event.payouts.first if eligible_event.can_request_payout?
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
test "manual_payout? returns false for Stripe payouts" do
|
||||||
|
payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100,
|
||||||
|
stripe_payout_id: "tr_123")
|
||||||
|
assert_not payout.manual_payout?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "manual_payout? returns false when no stripe_payout_id" do
|
||||||
|
payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100)
|
||||||
|
assert_not payout.manual_payout?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "mark_as_manually_processed! updates status and creates manual ID" do
|
||||||
|
payout = Payout.create!(user: @user, event: @event, amount_cents: 1000, fee_cents: 100, status: :pending)
|
||||||
|
payout.mark_as_manually_processed!
|
||||||
|
|
||||||
|
assert payout.completed?
|
||||||
|
assert payout.manual_payout?
|
||||||
|
assert_match /MANUAL_/, payout.stripe_payout_id
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -69,4 +69,40 @@ class PayoutServiceTest < ActiveSupport::TestCase
|
|||||||
assert_equal :paid, earning1.reload.status
|
assert_equal :paid, earning1.reload.status
|
||||||
assert_equal :paid, earning2.reload.status
|
assert_equal :paid, earning2.reload.status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "process! handles manual processing when user has no stripe account" do
|
||||||
|
# Create a user without a stripe account
|
||||||
|
user_without_stripe = User.create!(
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "password123",
|
||||||
|
is_professionnal: true
|
||||||
|
)
|
||||||
|
|
||||||
|
event = Event.create!(
|
||||||
|
user: user_without_stripe,
|
||||||
|
name: "Test Event",
|
||||||
|
slug: "test-event",
|
||||||
|
description: "Test event description",
|
||||||
|
venue_name: "Test Venue",
|
||||||
|
venue_address: "Test Address",
|
||||||
|
latitude: 48.8566,
|
||||||
|
longitude: 2.3522,
|
||||||
|
start_time: 1.day.ago,
|
||||||
|
end_time: 1.hour.ago,
|
||||||
|
state: :published
|
||||||
|
)
|
||||||
|
|
||||||
|
payout = Payout.create!(user: user_without_stripe, event: event, amount_cents: 9000, fee_cents: 1000, status: :pending)
|
||||||
|
|
||||||
|
# Mock that Stripe is not available for this user
|
||||||
|
user_without_stripe.stubs(:has_stripe_account?).returns(false)
|
||||||
|
|
||||||
|
service = PayoutService.new(payout)
|
||||||
|
service.process!
|
||||||
|
|
||||||
|
payout.reload
|
||||||
|
assert_equal :completed, payout.status
|
||||||
|
assert payout.manual_payout?
|
||||||
|
assert_match /MANUAL_/, payout.stripe_payout_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user