+
+ <%= render 'components/breadcrumb', crumbs: [
+ { name: 'Dashboard', path: dashboard_path },
+ { name: 'Événements', path: promoter_events_path },
+ { name: @event.name, path: promoter_event_path(@event) },
+ { name: 'Types de billets', path: promoter_event_ticket_types_path(@event) },
+ { name: @ticket_type.name, path: nil }
+ ] %>
+
diff --git a/config/routes.rb b/config/routes.rb
index b152192..d4bf3be 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -81,6 +81,7 @@ Rails.application.routes.draw do
patch :unpublish
patch :cancel
patch :mark_sold_out
+ post :duplicate
end
# Nested ticket types routes
diff --git a/docs/pricing-model-recommendations.md b/docs/pricing-model-recommendations.md
index d90bee3..cffe862 100644
--- a/docs/pricing-model-recommendations.md
+++ b/docs/pricing-model-recommendations.md
@@ -3,28 +3,32 @@
## Current Problem Analysis
### Current Model: €1 Fixed Fee Per Order
+
- **Revenue**: €1.00 per order (regardless of ticket price)
- **Stripe Costs**: 1.15% + €0.25 per transaction
- **Result**: Losing money on higher-priced tickets
### Financial Impact Examples
+
| Ticket Price | Current Revenue | Stripe Fees | Net Profit/Loss |
-|--------------|----------------|-------------|-----------------|
-| €20 | €1.00 | €0.48 | **+€0.52** ✅ |
-| €50 | €1.00 | €0.83 | **+€0.17** ⚠️ |
-| €100 | €1.00 | €1.40 | **-€0.40** ❌ |
-| €200 | €1.00 | €2.55 | **-€1.55** ❌ |
+| ------------ | --------------- | ----------- | --------------- |
+| €20 | €1.00 | €0.48 | **+€0.52** ✅ |
+| €50 | €1.00 | €0.83 | **+€0.17** ⚠️ |
+| €100 | €1.00 | €1.40 | **-€0.40** ❌ |
+| €200 | €1.00 | €2.55 | **-€1.55** ❌ |
**Critical Issue**: We lose money on any ticket above €65 (€1.00 - (€65 × 1.15% + €0.25) = -€0.00)
## Recommended Pricing Models
### Model 1: Percentage-Based (Recommended)
+
```
Platform Fee = 3-5% of ticket price
```
**Advantages:**
+
- Always profitable regardless of ticket price
- Scales naturally with event value
- Simple for promoters to understand
@@ -33,17 +37,19 @@ Platform Fee = 3-5% of ticket price
**Financial Impact:**
| Ticket Price | 3.5% Fee | Stripe Costs | Net Profit |
|--------------|----------|--------------|------------|
-| €20 | €0.70 | €0.48 | €0.22 |
-| €50 | €1.75 | €0.83 | €0.92 |
-| €100 | €3.50 | €1.40 | €2.10 |
-| €200 | €7.00 | €2.55 | €4.45 |
+| €20 | €0.70 | €0.48 | €0.22 |
+| €50 | €1.75 | €0.83 | €0.92 |
+| €100 | €3.50 | €1.40 | €2.10 |
+| €200 | €7.00 | €2.55 | €4.45 |
### Model 2: Hybrid Model (Best)
+
```
Platform Fee = €1.50 + 2% of ticket price
```
**Advantages:**
+
- Higher base fee covers Stripe fixed costs
- Percentage component scales with value
- Better margins on low-priced tickets
@@ -52,12 +58,13 @@ Platform Fee = €1.50 + 2% of ticket price
**Financial Impact:**
| Ticket Price | Hybrid Fee | Stripe Costs | Net Profit |
|--------------|------------|--------------|------------|
-| €20 | €1.90 | €0.48 | €1.42 |
-| €50 | €2.50 | €0.83 | €1.67 |
-| €100 | €3.50 | €1.40 | €2.10 |
-| €200 | €5.50 | €2.55 | €2.95 |
+| €20 | €1.90 | €0.48 | €1.42 |
+| €50 | €2.50 | €0.83 | €1.67 |
+| €100 | €3.50 | €1.40 | €2.10 |
+| €200 | €5.50 | €2.55 | €2.95 |
### Model 3: Tiered Flat Fees
+
```
€0-25: €1.50 fee
€26-75: €2.50 fee
@@ -66,32 +73,36 @@ Platform Fee = €1.50 + 2% of ticket price
```
**Advantages:**
+
- Predictable fees for promoters
- Simple pricing tiers
- Better than current model
**Disadvantages:**
+
- Less scalable than percentage
- Requires tier management
## Industry Benchmarks
-| Platform | Fee Structure | Effective Rate (€50 ticket) |
-|-------------|----------------------------|------------------------------|
-| Eventbrite | 3.7% + €0.59 | €2.44 |
-| Universe | 2.5% + €0.49 | €1.74 |
-| Ticketfly | 4% + €0.99 | €2.99 |
-| **Recommended** | **3.5%** | **€1.75** |
+| Platform | Fee Structure | Effective Rate (€50 ticket) |
+| --------------- | ------------- | --------------------------- |
+| Eventbrite | 3.7% + €0.59 | €2.44 |
+| Universe | 2.5% + €0.49 | €1.74 |
+| Ticketfly | 4% + €0.99 | €2.99 |
+| **Recommended** | **3.5%** | **€1.75** |
## Implementation Strategy
### Phase 1: Immediate Implementation (Week 1-2)
+
1. **Switch to 3.5% percentage model** for new events
2. **Grandfathering**: Existing published events keep €1 pricing
3. **Communication**: Notify promoters 1 week before change
4. **Update pricing display** on event creation and checkout pages
### Phase 2: Optimization (Month 2-3)
+
1. **A/B Test different rates**: 3%, 3.5%, 4%
2. **Monitor metrics**:
- Promoter signup rate
@@ -101,6 +112,7 @@ Platform Fee = €1.50 + 2% of ticket price
3. **Adjust based on data**
### Phase 3: Premium Tiers (Month 4-6)
+
1. **Volume discounts** for high-performing promoters:
- Standard: 3.5%
- Bronze (€5K+ monthly): 3%
@@ -112,6 +124,7 @@ Platform Fee = €1.50 + 2% of ticket price
## Revenue Projections
### Current State (€1 fixed)
+
- Average ticket price: €35
- Monthly orders: 1,000
- Monthly revenue: €1,000
@@ -119,11 +132,13 @@ Platform Fee = €1.50 + 2% of ticket price
- **Net monthly profit: €372**
### With 3.5% Model
+
- Monthly revenue: €1,225 (3.5% × €35 × 1,000)
- Monthly Stripe costs: €628
- **Net monthly profit: €597** (+60% increase)
### With Growth (3.5% model + 20% more events)
+
- Monthly orders: 1,200 (20% growth from competitive pricing)
- Monthly revenue: €1,470
- **Net monthly profit: €842** (+126% increase)
@@ -131,6 +146,7 @@ Platform Fee = €1.50 + 2% of ticket price
## Technical Implementation
### Database Changes
+
```ruby
# Add to events table
add_column :events, :commission_rate, :decimal, precision: 5, scale: 4, default: 0.035
@@ -140,6 +156,7 @@ add_column :users, :commission_tier, :string, default: 'standard'
```
### Fee Calculation Logic
+
```ruby
class Order < ApplicationRecord
def calculate_platform_fee
@@ -148,7 +165,7 @@ class Order < ApplicationRecord
base_amount = total_amount_cents / 100.0
(base_amount * commission_rate * 100).to_i # return in cents
end
-
+
def platform_fee_euros
calculate_platform_fee / 100.0
end
@@ -156,6 +173,7 @@ end
```
### Promoter Dashboard Updates
+
- Show fee breakdown on event creation
- Display projected fees during ticket setup
- Add revenue vs. fees analytics
@@ -164,9 +182,11 @@ end
## Communication Plan
### Email to Existing Promoters
+
**Subject**: "Important Pricing Update - New Fair Fee Structure"
**Key Points**:
+
- Current model loses money on higher-priced tickets
- New model ensures platform sustainability
- Better features and support with improved revenue
@@ -174,6 +194,7 @@ end
- Competitive with industry standards
### Website Updates
+
- Update pricing page with clear fee calculator
- Add FAQ about fee structure
- Transparency about what fees cover (development, support, payment processing)
@@ -181,11 +202,13 @@ end
## Risk Mitigation
### Potential Issues
+
1. **Promoter backlash**: Higher fees on expensive tickets
2. **Competitor advantage**: Other platforms with lower fees
3. **Reduced event creation**: Promoters may create fewer events
### Mitigation Strategies
+
1. **Value communication**: Emphasize platform improvements and reliability
2. **Competitive analysis**: Position as "fair and sustainable" vs. competitors
3. **Volume incentives**: Quick path to reduced rates for active promoters
@@ -194,12 +217,14 @@ end
## Success Metrics
### Financial KPIs
+
- Monthly recurring revenue growth
- Average revenue per transaction
- Profit margin improvement
- Customer acquisition cost vs. lifetime value
### Product KPIs
+
- Promoter retention rate
- New event creation volume
- Average ticket prices
@@ -221,6 +246,7 @@ The current €1 fixed fee model is financially unsustainable and actually costs
### Current Approach vs Industry Standard
**Current Model (Not Recommended)**:
+
```
Customer pays: €50 + €1 fee = €51
Promoter receives: €50
@@ -228,6 +254,7 @@ Platform keeps: €1
```
**Industry Standard (Recommended)**:
+
```
Customer pays: €50 (clean price)
Platform keeps: €1.75 (3.5% of €50)
@@ -237,18 +264,21 @@ Promoter receives: €48.25
### How Major Platforms Handle Fees
**Eventbrite:**
+
- Promoter sets: €50 ticket
-- Customer pays: €50
+- Customer pays: €50
- Eventbrite keeps: €1.85 (3.7%)
- Promoter receives: €48.15
**Ticketmaster:**
-- Promoter sets: €50 ticket
+
+- Promoter sets: €50 ticket
- Customer pays: €50
- Ticketmaster keeps: €5-7.50 (10-15%)
- Promoter receives: €42.50-45
**Universe (by Ticketmaster):**
+
- Promoter sets: €50 ticket
- Customer pays: €50
- Universe keeps: €1.74 (2.5% + €0.49)
@@ -257,16 +287,19 @@ Promoter receives: €48.25
### Why Deducting from Payout is Better
#### 1. Customer Experience
+
- **Price transparency**: Customer sees exactly what they expect to pay
- **No surprise fees**: Reduces cart abandonment
- **Competitive pricing**: Easier to compare with other events
#### 2. Promoter Benefits
+
- **Marketing simplicity**: Can advertise clean prices
- **Psychological pricing**: €50 sounds better than €51.75
- **Competitive advantage**: Not adding extra fees to customer
#### 3. Platform Benefits
+
- **Higher conversion rates**: No fee-shock at checkout
- **Better promoter adoption**: Easier to sell to event organizers
- **Industry standard**: Follows established practices
@@ -274,14 +307,17 @@ Promoter receives: €48.25
### Psychological Impact
**Adding Fees to Customer (Current)**:
+
- Customer thinks: "€50 ticket... oh wait, €51.75 total" 😤
- Cart abandonment risk
-**Deducting from Payout (Recommended)**:
+**Deducting from Payout (Recommended)**:
+
- Customer thinks: "€50 ticket, €50 total" 😊
- Smooth purchase experience
### Promoter Dashboard Display
+
```
Ticket Price: €50.00
Platform Fee (3.5%): -€1.75
@@ -289,10 +325,12 @@ Your Earnings per Ticket: €48.25
```
### Communication to Promoters
+
**Before:** "Platform charges €1 per order to customers"
**After:** "Set your desired revenue per ticket, we handle the rest"
**Example:**
+
- Promoter wants €48.25 net per ticket
- They should set ticket price at €50
- Customer pays €50, promoter gets €48.25
@@ -306,52 +344,56 @@ Our main competitor charges a simple €1 flat fee per order. Here's how our mod
### Hybrid Model vs Competitor Analysis
-**Hybrid Model**: €1.50 + 2% of ticket price
+**Hybrid Model**: €1.50 + 2% of ticket price
**Competitor**: €1.00 flat fee
| Ticket Price | Competitor Fee | Hybrid Fee | Difference | Competitive Position |
-|--------------|----------------|------------|------------|---------------------|
-| €10 | €1.00 | €1.70 | +€0.70 | More expensive |
-| €25 | €1.00 | €2.00 | +€1.00 | More expensive |
-| €50 | €1.00 | €2.50 | +€1.50 | More expensive |
+| ------------ | -------------- | ---------- | ---------- | -------------------- |
+| €10 | €1.00 | €1.70 | +€0.70 | More expensive |
+| €25 | €1.00 | €2.00 | +€1.00 | More expensive |
+| €50 | €1.00 | €2.50 | +€1.50 | More expensive |
| **€75** | **€1.00** | **€3.00** | **+€2.00** | **Break-even point** |
-| €100 | €1.00 | €3.50 | +€2.50 | Much more expensive |
+| €100 | €1.00 | €3.50 | +€2.50 | Much more expensive |
### Alternative Competitive Models
#### Option 1: Low-End Competitive Model
+
```
-Platform Fee = €0.50 + 1.5% of ticket price
+ Platform Fee = €0.50 + 1.5% of ticket price
```
-| Ticket Price | Competitor Fee | Our Fee | Difference | Position |
-|--------------|----------------|---------|------------|----------|
-| €10 | €1.00 | €0.65 | **-€0.35** | ✅ **Cheaper** |
-| €25 | €1.00 | €0.88 | **-€0.12** | ✅ **Cheaper** |
+| Ticket Price | Competitor Fee | Our Fee | Difference | Position |
+| ------------ | -------------- | ------- | ---------- | ------------------ |
+| €10 | €1.00 | €0.65 | **-€0.35** | ✅ **Cheaper** |
+| €25 | €1.00 | €0.88 | **-€0.12** | ✅ **Cheaper** |
| €50 | €1.00 | €1.25 | +€0.25 | ⚠️ Slightly higher |
-| €100 | €1.00 | €2.00 | +€1.00 | More expensive |
+| €100 | €1.00 | €2.00 | +€1.00 | More expensive |
#### Option 2: Modified Hybrid Model
+
```
Platform Fee = €0.75 + 2.5% of ticket price
```
-| Ticket Price | Competitor Fee | Our Fee | Difference | Position |
-|--------------|----------------|---------|------------|----------|
-| €10 | €1.00 | €1.00 | **Equal** | ✅ Competitive |
-| €25 | €1.00 | €1.38 | +€0.38 | ⚠️ Slightly higher |
-| €40 | €1.00 | €1.75 | +€0.75 | **Break-even** |
+| Ticket Price | Competitor Fee | Our Fee | Difference | Position |
+| ------------ | -------------- | ------- | ---------- | ------------------- |
+| €10 | €1.00 | €1.00 | **Equal** | ✅ Competitive |
+| €25 | €1.00 | €1.38 | +€0.38 | ⚠️ Slightly higher |
+| €40 | €1.00 | €1.75 | +€0.75 | **Break-even** |
| €75 | €1.00 | €2.63 | +€1.63 | Much more expensive |
### Competitive Strategy Recommendations
#### 1. Value Differentiation Approach
+
Since we'll be more expensive on higher-priced tickets, focus on premium positioning:
**Value Proposition:**
+
- "We're not the cheapest, we're the most complete"
- Advanced analytics dashboard
-- Real-time sales tracking
+- Real-time sales tracking
- Professional promoter tools
- Superior customer support
- Platform reliability and sustainability
@@ -359,12 +401,14 @@ Since we'll be more expensive on higher-priced tickets, focus on premium positio
#### 2. Market Segmentation Strategy
**Target Market Positioning:**
+
- **Competitor**: Best for small, simple events (€10-30 tickets)
- **Us**: Best for professional events (€40+ tickets) with serious promoters
#### 3. Hybrid Competitive Approach
**Tiered Offering:**
+
- **Basic Plan**: Match competitor at €1 flat fee (limited features)
- **Professional Plan**: Hybrid model with premium features
- **Enterprise Plan**: Custom pricing with full feature set
@@ -372,6 +416,7 @@ Since we'll be more expensive on higher-priced tickets, focus on premium positio
#### 4. Volume-Based Competitive Response
**Free Tier Strategy:**
+
- First 3 events per month at competitor's €1 rate
- Volume discounts for high-activity promoters
- Loyalty rewards for long-term customers
@@ -381,15 +426,18 @@ Since we'll be more expensive on higher-priced tickets, focus on premium positio
#### "Choose Your Business Model" Campaign
**For Simple Events (Under €40):**
+
- "Need basic ticketing? Our competitor works fine"
- "Pay €1 flat fee for simple events"
**For Professional Events (€40+):**
+
- "Serious about your business? You need serious tools"
- "Fair percentage-based pricing"
- "Advanced analytics, marketing tools, priority support"
#### Brand Positioning Statement
+
**"We're the Shopify of Events - Built for Growth"**
This positions us as the premium option for serious promoters while acknowledging the competitor's advantage on small events.
@@ -399,12 +447,14 @@ This positions us as the premium option for serious promoters while acknowledgin
Given the competitive landscape, we recommend **Option 1** (€0.50 + 1.5%):
**Advantages:**
+
- Competitive on low-priced tickets
- Still profitable at all price points
- Better positioning against main competitor
- Appeals to both small and large event organizers
**Financial Impact:**
+
- Lower fees on tickets under €33
- Competitive fees on tickets €33-66
-- Premium pricing on high-value tickets justified by features
\ No newline at end of file
+- Premium pricing on high-value tickets justified by features
diff --git a/test/controllers/promoter/events_controller_test.rb b/test/controllers/promoter/events_controller_test.rb
index 2a6928a..1bf2f01 100644
--- a/test/controllers/promoter/events_controller_test.rb
+++ b/test/controllers/promoter/events_controller_test.rb
@@ -26,5 +26,103 @@ class Promoter::EventsControllerTest < ActionDispatch::IntegrationTest
assert_not_includes assigns(:events), other_event
end
+ test "should duplicate an event with ticket types" do
+ sign_in @promoter
+
+ # Create ticket types for the event
+ ticket_type1 = TicketType.create!(
+ name: "Standard Ticket",
+ description: "A standard ticket for the event with all the basic access",
+ price_cents: 2000,
+ quantity: 100,
+ sale_start_at: 1.day.ago,
+ sale_end_at: @event.start_time - 1.hour,
+ event: @event
+ )
+
+ ticket_type2 = TicketType.create!(
+ name: "VIP Ticket",
+ description: "A VIP ticket for the event with special access",
+ price_cents: 5000,
+ quantity: 50,
+ sale_start_at: 1.day.ago,
+ sale_end_at: @event.start_time - 1.hour,
+ event: @event
+ )
+
+ # Verify that ticket types were created successfully
+ assert ticket_type1.valid?
+ assert ticket_type2.valid?
+
+ # Duplicate the event
+ assert_difference("Event.count", 1) do
+ post duplicate_promoter_event_path(@event), params: { clone_ticket_types: "true" }
+ end
+
+ # Check that the new event was created
+ assert_redirected_to edit_promoter_event_path(Event.last)
+ assert_equal "Événement dupliqué avec succès! Vous pouvez maintenant modifier les détails de l'événement copié.", flash[:notice]
+
+ # Check that the new event has the correct attributes
+ new_event = Event.last
+ assert_equal "Copie de #{@event.name}", new_event.name
+ assert_equal "draft", new_event.state
+ assert_equal @event.venue_name, new_event.venue_name
+ assert_equal @event.venue_address, new_event.venue_address
+
+ # Check that ticket types were duplicated
+ assert_equal 2, new_event.ticket_types.count
+ assert_equal "Standard Ticket", new_event.ticket_types.find_by(name: "Standard Ticket").name
+ assert_equal "VIP Ticket", new_event.ticket_types.find_by(name: "VIP Ticket").name
+ end
+
+ test "should duplicate an event without ticket types" do
+ sign_in @promoter
+
+ # Create ticket types for the event
+ ticket_type1 = TicketType.create!(
+ name: "Standard Ticket",
+ description: "A standard ticket for the event with all the basic access",
+ price_cents: 2000,
+ quantity: 100,
+ sale_start_at: 1.day.ago,
+ sale_end_at: @event.start_time - 1.hour,
+ event: @event
+ )
+
+ ticket_type2 = TicketType.create!(
+ name: "VIP Ticket",
+ description: "A VIP ticket for the event with special access",
+ price_cents: 5000,
+ quantity: 50,
+ sale_start_at: 1.day.ago,
+ sale_end_at: @event.start_time - 1.hour,
+ event: @event
+ )
+
+ # Verify that ticket types were created successfully
+ assert ticket_type1.valid?
+ assert ticket_type2.valid?
+
+ # Duplicate the event without ticket types
+ assert_difference("Event.count", 1) do
+ post duplicate_promoter_event_path(@event), params: { clone_ticket_types: "false" }
+ end
+
+ # Check that the new event was created
+ assert_redirected_to edit_promoter_event_path(Event.last)
+ assert_equal "Événement dupliqué avec succès! Vous pouvez maintenant modifier les détails de l'événement copié.", flash[:notice]
+
+ # Check that the new event has the correct attributes
+ new_event = Event.last
+ assert_equal "Copie de #{@event.name}", new_event.name
+ assert_equal "draft", new_event.state
+ assert_equal @event.venue_name, new_event.venue_name
+ assert_equal @event.venue_address, new_event.venue_address
+
+ # Check that ticket types were NOT duplicated
+ assert_equal 0, new_event.ticket_types.count
+ end
+
# Add tests for new, create, etc. as needed
end
diff --git a/test/models/event_test.rb b/test/models/event_test.rb
index 0f94404..8249bd1 100755
--- a/test/models/event_test.rb
+++ b/test/models/event_test.rb
@@ -271,4 +271,50 @@ class EventTest < ActiveSupport::TestCase
event = Event.new
assert_not event.allow_booking_during_event?
end
+
+ test "should duplicate event with ticket types" do
+ user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
+ event = Event.create!(name: "Original Event", slug: "original", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.week.from_now, state: :published)
+
+ # Create ticket types
+ ticket_type1 = TicketType.create!(name: "Standard", description: "A standard ticket for the event", price_cents: 2000, quantity: 100, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
+ ticket_type2 = TicketType.create!(name: "VIP", description: "A VIP ticket for the event", price_cents: 5000, quantity: 50, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
+
+ # Duplicate the event
+ duplicated_event = event.duplicate
+
+ # Check that duplication was successful
+ assert_not_nil duplicated_event
+ assert_equal "Copie de #{event.name}", duplicated_event.name
+ assert_equal "draft", duplicated_event.state
+ assert_equal event.venue_name, duplicated_event.venue_name
+ assert_equal event.venue_address, duplicated_event.venue_address
+
+ # Check that ticket types were duplicated
+ assert_equal 2, duplicated_event.ticket_types.count
+ assert_equal "Standard", duplicated_event.ticket_types.find_by(name: "Standard").name
+ assert_equal "VIP", duplicated_event.ticket_types.find_by(name: "VIP").name
+ end
+
+ test "should duplicate event without ticket types" do
+ user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
+ event = Event.create!(name: "Original Event", slug: "original", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.week.from_now, state: :published)
+
+ # Create ticket types
+ ticket_type1 = TicketType.create!(name: "Standard", description: "A standard ticket for the event", price_cents: 2000, quantity: 100, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
+ ticket_type2 = TicketType.create!(name: "VIP", description: "A VIP ticket for the event", price_cents: 5000, quantity: 50, sale_start_at: 1.day.ago, sale_end_at: event.start_time - 1.hour, event: event)
+
+ # Duplicate the event without ticket types
+ duplicated_event = event.duplicate(clone_ticket_types: false)
+
+ # Check that duplication was successful
+ assert_not_nil duplicated_event
+ assert_equal "Copie de #{event.name}", duplicated_event.name
+ assert_equal "draft", duplicated_event.state
+ assert_equal event.venue_name, duplicated_event.venue_name
+ assert_equal event.venue_address, duplicated_event.venue_address
+
+ # Check that ticket types were NOT duplicated
+ assert_equal 0, duplicated_event.ticket_types.count
+ end
end
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
index 40c7e16..6db2124 100644
--- a/test/models/order_test.rb
+++ b/test/models/order_test.rb
@@ -469,7 +469,7 @@ class OrderTest < ActiveSupport::TestCase
assert_equal "active", ticket2.status
end
- test "calculate_total! should sum ticket prices plus 1€ service fee" do
+ test "calculate_total! should sum ticket prices only (platform fee deducted from promoter payout)" do
order = Order.create!(
user: @user, event: @event, total_amount_cents: 0,
status: "draft", payment_attempts: 0
@@ -506,7 +506,80 @@ class OrderTest < ActiveSupport::TestCase
order.calculate_total!
order.reload
- assert_equal 3100, order.total_amount_cents # 2 tickets * 1500 cents + 100 cents (1€ fee)
+ assert_equal 3000, order.total_amount_cents # 2 tickets * 1500 cents (no service fee added to customer)
+ end
+
+ test "platform_fee_cents should calculate €0.50 + 1.5% per ticket" do
+ order = Order.create!(
+ user: @user, event: @event, total_amount_cents: 0,
+ status: "draft", payment_attempts: 0
+ )
+
+ ticket_type1 = TicketType.create!(
+ name: "Cheap Ticket",
+ description: "Cheap ticket type",
+ price_cents: 1000, # €10
+ quantity: 10,
+ sale_start_at: Time.current,
+ sale_end_at: Time.current + 1.day,
+ requires_id: false,
+ event: @event
+ )
+
+ ticket_type2 = TicketType.create!(
+ name: "Expensive Ticket",
+ description: "Expensive ticket type",
+ price_cents: 5000, # €50
+ quantity: 10,
+ sale_start_at: Time.current,
+ sale_end_at: Time.current + 1.day,
+ requires_id: false,
+ event: @event
+ )
+
+ ticket1 = Ticket.create!(order: order, ticket_type: ticket_type1, status: "draft", first_name: "John", last_name: "Doe")
+ ticket2 = Ticket.create!(order: order, ticket_type: ticket_type2, status: "draft", first_name: "Jane", last_name: "Doe")
+
+ expected_fee = (50 + (1000 * 0.015).to_i) + (50 + (5000 * 0.015).to_i) # 50+15 + 50+75 = 190
+ assert_equal 190, order.platform_fee_cents
+ end
+
+ test "promoter_payout_cents should be total minus platform fee" do
+ order = Order.create!(
+ user: @user, event: @event, total_amount_cents: 3000,
+ status: "paid", payment_attempts: 0
+ )
+
+ ticket_type = TicketType.create!(
+ name: "Test Ticket",
+ description: "Test ticket",
+ price_cents: 1500,
+ quantity: 10,
+ sale_start_at: Time.current,
+ sale_end_at: Time.current + 1.day,
+ requires_id: false,
+ event: @event
+ )
+
+ Ticket.create!(order: order, ticket_type: ticket_type, status: "active", first_name: "John", last_name: "Doe")
+ Ticket.create!(order: order, ticket_type: ticket_type, status: "active", first_name: "Jane", last_name: "Doe")
+
+ order.calculate_total! # Should still be 3000
+ expected_payout = 3000 - (50 + (1500 * 0.015).to_i) * 2 # 3000 - (50+22.5≈22)*2 = 3000 - 144 = 2856
+ assert_equal 2856, order.promoter_payout_cents
+ end
+
+ test "platform_fee_euros should convert cents to euros" do
+ order = Order.new(total_amount_cents: 0)
+ # Assuming one €10 ticket: 50 + 150 = 200 cents = €2.00
+ def order.platform_fee_cents; 200; end
+ assert_equal 2.0, order.platform_fee_euros
+ end
+
+ test "promoter_payout_euros should convert cents to euros" do
+ order = Order.new(total_amount_cents: 10000)
+ def order.platform_fee_cents; 500; end
+ assert_equal 95.0, order.promoter_payout_euros
end
# === Stripe Integration Tests (Mock) ===
diff --git a/test/services/stripe_invoice_service_test.rb b/test/services/stripe_invoice_service_test.rb
index c89f6eb..60e914e 100644
--- a/test/services/stripe_invoice_service_test.rb
+++ b/test/services/stripe_invoice_service_test.rb
@@ -151,7 +151,7 @@ class StripeInvoiceServiceTest < ActiveSupport::TestCase
mock_invoice.stubs(:finalize_invoice).returns(mock_invoice)
mock_invoice.expects(:pay)
Stripe::Invoice.expects(:create).returns(mock_invoice)
- Stripe::InvoiceItem.expects(:create).twice # Once for tickets, once for service fee
+ Stripe::InvoiceItem.expects(:create).once # Only for tickets, no service fee
result = @service.create_post_payment_invoice
assert_not_nil result
@@ -173,7 +173,7 @@ class StripeInvoiceServiceTest < ActiveSupport::TestCase
mock_invoice.stubs(:finalize_invoice).returns(mock_invoice)
mock_invoice.expects(:pay)
Stripe::Invoice.expects(:create).returns(mock_invoice)
- Stripe::InvoiceItem.expects(:create).twice # Once for tickets, once for service fee
+ Stripe::InvoiceItem.expects(:create).once # Only for tickets, no service fee
result = @service.create_post_payment_invoice
assert_not_nil result
@@ -261,7 +261,7 @@ class StripeInvoiceServiceTest < ActiveSupport::TestCase
mock_invoice.expects(:pay)
Stripe::Invoice.expects(:create).with(expected_invoice_data).returns(mock_invoice)
- Stripe::InvoiceItem.expects(:create).twice # Once for tickets, once for service fee
+ Stripe::InvoiceItem.expects(:create).once # Only for tickets, no service fee
result = @service.create_post_payment_invoice
assert_not_nil result
@@ -300,7 +300,7 @@ class StripeInvoiceServiceTest < ActiveSupport::TestCase
})
Stripe::Invoice.expects(:create).returns(mock_invoice)
- Stripe::InvoiceItem.expects(:create).twice # Once for tickets, once for service fee
+ Stripe::InvoiceItem.expects(:create).once # Only for tickets, no service fee
mock_invoice.expects(:finalize_invoice).returns(mock_finalized_invoice)
result = @service.create_post_payment_invoice