Files
aperonight/AGENTS.md
kbe 66fffa8676 fix(promotion code): Cap the minimum invoice for Stripe
Stripe does not support negative invoices, so to
allow correct invoice generation, we apply dismiss
negative invoices.
2025-09-29 23:55:21 +02:00

19 KiB
Executable File

Aperonight - Technical Documentation for AI Agents

🤖 Agent Implementation Guide

This document provides technical details for AI agents working on the Aperonight ticket selling system.

🏗️ System Architecture

Core Components

1. User Management (app/models/user.rb)

  • Devise Integration: Complete authentication system with registration, login, password reset
  • Professional Users: is_professionnal field for event promoters with enhanced permissions
  • Onboarding System: Multi-step onboarding process with onboarding_completed tracking
  • Stripe Integration: stripe_customer_id for accounting and invoice management
  • Relationships: Users can create events, purchase tickets, and manage promotion codes
  • Validations: Email format, password strength, optional name fields, company information

2. Event System (app/models/event.rb)

  • States: draft, published, canceled, sold_out with enum management
  • Geographic Data: Latitude/longitude for venue mapping
  • Relationships: Belongs to user, has many ticket types, tickets through ticket types, and orders
  • Scopes: Featured events, published events, upcoming events with proper ordering
  • Duplication: Event duplication functionality for similar events

3. Order Management (app/models/order.rb)

  • Order States: draft, pending_payment, paid, completed, cancelled, expired
  • Payment Processing: Stripe integration with payment attempt tracking
  • Platform Fees: €0.50 fixed + 1.5% per ticket automatic calculation
  • Expiration: 15-minute draft order expiration with automatic cleanup
  • Promotion Integration: Support for discount code application
  • Invoice Generation: Automatic Stripe invoice creation for accounting

4. Promotion Code System (app/models/promotion_code.rb)

  • Discount Management: Fixed amount discounts (stored in cents, displayed in euros)
  • Usage Controls: Per-event and per-user association with usage limits
  • Expiration: Date-based expiration with active/inactive status management
  • Validation: Real-time validation during checkout process
  • Tracking: Complete usage tracking and analytics

5. Ticket Management

  • TicketType (app/models/ticket_type.rb): Defines ticket categories with pricing, quantity, sale periods
  • Ticket (app/models/ticket.rb): Individual tickets with unique QR codes, status tracking, price storage
  • Order Association: Tickets now belong to orders for better transaction management

6. Payment Processing (app/controllers/orders_controller.rb)

  • Order-Based Workflow: Complete shift from direct ticket purchase to order-based system
  • Stripe Integration: Complete checkout session creation and payment confirmation
  • Session Management: Proper handling of payment success/failure with order and ticket generation
  • Security: Authentication required, cart validation, availability checking
  • Invoice Service: Post-payment invoice generation with StripeInvoiceService

Database Schema Key Points

-- Users table (enhanced with professional features)
CREATE TABLE users (
  id bigint PRIMARY KEY,
  email varchar(255) UNIQUE NOT NULL,
  encrypted_password varchar(255) NOT NULL,
  first_name varchar(255),
  last_name varchar(255),
  is_professionnal boolean DEFAULT false,
  onboarding_completed boolean DEFAULT false,
  stripe_customer_id varchar(255),
  company_name varchar(255),
  -- Devise fields: confirmation, reset tokens, etc.
);

-- Events table (enhanced with order management)
CREATE TABLE events (
  id bigint PRIMARY KEY,
  user_id bigint REFERENCES users(id),
  name varchar(100) NOT NULL,
  slug varchar(100) NOT NULL,
  description text(1000) NOT NULL,
  venue_name varchar(100) NOT NULL,
  venue_address varchar(200) NOT NULL,
  latitude decimal(10,8) NOT NULL,
  longitude decimal(11,8) NOT NULL,
  start_time datetime NOT NULL,
  end_time datetime,
  state integer DEFAULT 0, -- enum: draft=0, published=1, canceled=2, sold_out=3
  featured boolean DEFAULT false,
  image varchar(500)
);

-- Order management system (new core table)
CREATE TABLE orders (
  id bigint PRIMARY KEY,
  user_id bigint REFERENCES users(id),
  event_id bigint REFERENCES events(id),
  status varchar(255) DEFAULT 'draft',
  total_amount_cents integer DEFAULT 0,
  platform_fee_cents integer DEFAULT 0,
  payment_attempts integer DEFAULT 0,
  expires_at timestamp,
  last_payment_attempt_at timestamp,
  stripe_checkout_session_id varchar(255),
  stripe_invoice_id varchar(255)
);

-- Promotion codes table (new discount system)
CREATE TABLE promotion_codes (
  id bigint PRIMARY KEY,
  code varchar(255) UNIQUE NOT NULL,
  discount_amount_cents integer DEFAULT 0,
  expires_at datetime,
  active boolean DEFAULT true,
  usage_limit integer,
  uses_count integer DEFAULT 0,
  user_id bigint REFERENCES users(id),
  event_id bigint REFERENCES events(id)
);

-- Order-promotion code join table
CREATE TABLE order_promotion_codes (
  order_id bigint REFERENCES orders(id),
  promotion_code_id bigint REFERENCES promotion_codes(id)
);

-- Ticket types define pricing and availability
CREATE TABLE ticket_types (
  id bigint PRIMARY KEY,
  event_id bigint REFERENCES events(id),
  name varchar(255) NOT NULL,
  description text,
  price_cents integer NOT NULL,
  quantity integer NOT NULL,
  sale_start_at datetime,
  sale_end_at datetime,
  requires_id boolean DEFAULT false,
  minimum_age integer
);

-- Individual tickets with QR codes (enhanced with order association)
CREATE TABLE tickets (
  id bigint PRIMARY KEY,
  user_id bigint REFERENCES users(id),
  order_id bigint REFERENCES orders(id),
  ticket_type_id bigint REFERENCES ticket_types(id),
  qr_code varchar(255) UNIQUE NOT NULL,
  price_cents integer NOT NULL,
  status varchar(255) DEFAULT 'active' -- active, used, expired, refunded
);

🎯 Key Implementation Details

1. Dashboard Metrics (app/controllers/pages_controller.rb)

# User-specific metrics with optimized queries
@booked_events = current_user.tickets
                             .joins(:ticket_type, :event)
                             .where(events: { state: :published })
                             .count

# Event counts for different timeframes
@events_today = Event.published
                     .where("DATE(start_time) = ?", Date.current)
                     .count

# User's actual booked events (not just count)
@user_booked_events = Event.joins(ticket_types: :tickets)
                           .where(tickets: { user: current_user, status: 'active' })
                           .distinct
                           .limit(5)

2. Order Management Flow (app/controllers/orders_controller.rb)

Order Creation and Payment

  1. Cart-to-Order Conversion: Convert shopping cart to draft order with 15-minute expiration
  2. Platform Fee Calculation: Automatic calculation of €0.50 fixed + 1.5% per ticket
  3. Promotion Code Application: Real-time discount validation and application
  4. Stripe Checkout Session: Create payment session with order metadata
  5. Payment Retry: Support for multiple payment attempts with proper tracking
# Order creation with platform fees
def create
  @order = Order.new(order_params)
  @order.user = current_user
  @order.calculate_platform_fee
  @order.set_expiration

  if @order.save
    session = create_stripe_checkout_session(@order)
    redirect_to session.url, allow_other_host: true
  else
    render :new, status: :unprocessable_entity
  end
end

# Platform fee calculation
def calculate_platform_fee
  ticket_count = order_items.sum(:quantity)
  self.platform_fee_cents = 50 + (total_amount_cents * 0.015).to_i
end

Payment Confirmation and Invoice Generation

  1. Order Status Update: Transition from pending_payment to paid
  2. Ticket Generation: Create tickets associated with the order
  3. Stripe Invoice Creation: Async invoice generation for accounting
  4. Promotion Code Usage: Increment usage counters for applied codes

3. Enhanced Stripe Integration

StripeInvoiceService (app/services/stripe_invoice_service.rb)

  • Post-payment invoice creation with customer management
  • Line item processing with promotion discounts
  • PDF invoice URL generation for download
  • Accounting record synchronization
class StripeInvoiceService
  def initialize(order)
    @order = order
  end

  def create_invoice
    customer = find_or_create_stripe_customer
    invoice_items = create_invoice_items(customer)

    invoice = Stripe::Invoice.create({
      customer: customer.id,
      auto_advance: true,
      collection_method: 'charge_automatically'
    })

    @order.update(stripe_invoice_id: invoice.id)
    invoice.finalize_invoice
  end
end

4. Promotion Code System (app/models/promotion_code.rb)

Code Validation and Application

  • Real-time Validation: Check code validity, expiration, and usage limits
  • Discount Calculation: Apply fixed amount discounts to order totals
  • Usage Tracking: Increment usage counters and prevent overuse
  • Event-Specific Codes: Support for both global and event-specific codes
def valid_for_use?(user = nil, event = nil)
  return false unless active?
  return false if expired?
  return false if usage_limit_reached?
  return false if user.present? && !valid_for_user?(user)
  return false if event.present? && !valid_for_event?(event)
  true
end

def apply_discount(total_amount)
  [total_amount - discount_amount_cents, 0].max
end

5. Background Job Architecture

StripeInvoiceGenerationJob

  • Async invoice creation after successful payment
  • Retry logic with exponential backoff
  • Error handling and logging

ExpiredOrdersCleanupJob

  • Automatic cleanup of expired draft orders
  • Database maintenance and hygiene

EventReminderJob & EventReminderSchedulerJob

  • Automated event reminder emails
  • Scheduled notifications for upcoming events

6. PDF Ticket Generation (app/services/ticket_pdf_generator.rb)

class TicketPdfGenerator
  def generate
    Prawn::Document.new(page_size: [350, 600], margin: 20) do |pdf|
      # Header with branding
      pdf.fill_color "2D1B69"
      pdf.font "Helvetica", style: :bold, size: 24
      pdf.text "ApéroNight", align: :center

      # Event details
      pdf.text ticket.event.name, align: :center

      # QR Code generation
      qr_code_data = {
        ticket_id: ticket.id,
        qr_code: ticket.qr_code,
        event_id: ticket.event.id,
        user_id: ticket.user.id
      }.to_json

      qrcode = RQRCode::QRCode.new(qr_code_data)
      pdf.print_qr_code(qrcode, extent: 120, align: :center)
    end.render
  end
end

7. Frontend Architecture

Enhanced Stimulus Controllers

  • ticket_selection_controller.js: Advanced cart management with real-time updates
  • event_form_controller.js: Dynamic event creation with location services
  • countdown_controller.js: Order expiration countdown timers
  • event_duplication_controller.js: Event copying functionality
  • qr_code_controller.js: QR code display and scanning

Order-Based Cart Management

  • Session Storage: Preserves cart state during authentication flows
  • Real-time Updates: Dynamic total calculation with promotion codes
  • Validation: Client-side validation with server-side verification
  • Payment Flow: Seamless integration with Stripe checkout

🔧 Development Patterns

Model Validations

# Event validations
validates :name, presence: true, length: { minimum: 3, maximum: 100 }
validates :latitude, numericality: {
  greater_than_or_equal_to: -90,
  less_than_or_equal_to: 90
}

# Order validations with state management
validates :status, presence: true, inclusion: { in: %w[draft pending_payment paid completed cancelled expired] }
validate :order_not_expired, on: :create
before_validation :set_expiration, on: :create

# Promotion code validations
validates :code, presence: true, uniqueness: true
validates :discount_amount_cents, numericality: { greater_than_or_equal_to: 0 }
validate :expiration_date_cannot_be_in_the_past

# Ticket QR code generation
before_validation :generate_qr_code, on: :create
def generate_qr_code
  loop do
    self.qr_code = SecureRandom.uuid
    break unless Ticket.exists?(qr_code: qr_code)
  end
end

Controller Patterns

# Authentication for sensitive actions
before_action :authenticate_user!, only: [:checkout, :payment_success, :download_ticket]

# Professional user authorization
before_action :authenticate_professional!, only: [:create_promotion_code]

# Strong parameters with nested attributes
private
def order_params
  params.require(:order).permit(:promotion_code, order_items_attributes: [:ticket_type_id, :quantity])
end

# Platform fee calculation
def calculate_platform_fee
  ticket_count = order_items.sum(:quantity)
  self.platform_fee_cents = 50 + (total_amount_cents * 0.015).to_i
end

Service Layer Patterns

# Service for complex business logic
class StripeInvoiceService
  def initialize(order)
    @order = order
  end

  def call
    customer = find_or_create_stripe_customer
    create_invoice_items(customer)
    generate_invoice
  end

  private

  def find_or_create_stripe_customer
    if @order.user.stripe_customer_id.present?
      Stripe::Customer.retrieve(@order.user.stripe_customer_id)
    else
      customer = Stripe::Customer.create(email: @order.user.email)
      @order.user.update(stripe_customer_id: customer.id)
      customer
    end
  end
end

View Helpers and Partials

  • Metric Cards: Reusable component for dashboard statistics
  • Event Items: Consistent event display across pages
  • Flash Messages: Centralized notification system
  • Order Components: Reusable order display and management components

🚀 Deployment Considerations

Environment Variables

# Required for production
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
DATABASE_URL=mysql2://user:pass@host/db
RAILS_MASTER_KEY=...

# Rails 8 Solid Stack
SOLID_QUEUE_IN_PUMA=true
SOLID_CACHE_URL=redis://localhost:6379/0
SOLID_CABLE_URL=redis://localhost:6379/1

# Application Configuration
PLATFORM_FEE_FIXED_CENTS=50
PLATFORM_FEE_PERCENTAGE=1.5
ORDER_EXPIRATION_MINUTES=15

Database Indexes

-- Performance indexes for common queries
CREATE INDEX idx_events_published_start_time ON events (state, start_time);
CREATE INDEX idx_orders_user_status ON orders (user_id, status);
CREATE INDEX idx_orders_expires_at ON orders (expires_at) WHERE status = 'draft';
CREATE INDEX idx_tickets_user_status ON tickets (user_id, status);
CREATE INDEX idx_ticket_types_event ON ticket_types (event_id);
CREATE INDEX idx_promotion_codes_code ON promotion_codes (code);
CREATE INDEX idx_promotion_codes_active_expires ON promotion_codes (active, expires_at);

Security Considerations

  • CSRF Protection: Rails default protection enabled
  • Strong Parameters: All user inputs filtered
  • Authentication: Devise handles session security
  • Payment Security: Stripe handles sensitive payment data
  • Professional User Authorization: Role-based access control for event promoters
  • Order Expiration: Automatic cleanup of abandoned orders
  • Promotion Code Validation: Server-side validation with usage limits

Background Jobs

# Async invoice generation
StripeInvoiceGenerationJob.perform_later(order_id)

# Cleanup expired orders
ExpiredOrdersCleanupJob.perform_later

# Event reminders
EventReminderSchedulerJob.set(wait_until: event.start_time - 2.hours).perform_later(event_id)

🌐 API Layer

RESTful Endpoints

# API Namespacing for external integrations
namespace :api do
  namespace :v1 do
    resources :events, only: [:index, :show] do
      resources :ticket_types, only: [:index]
    end

    resources :carts, only: [:create, :show, :update]
    resources :orders, only: [:create, :show, :update]

    post '/promotion_codes/validate', to: 'promotion_codes#validate'
  end
end

API Authentication

  • Token-based authentication: API tokens for external integrations
  • Rate limiting: Request throttling for API endpoints
  • Versioning: Versioned API namespace for backward compatibility

🧪 Testing Strategy

Key Test Cases

  1. User Authentication: Registration, login, logout flows
  2. Professional User Onboarding: Multi-step onboarding process
  3. Event Creation: Validation, state management, relationships
  4. Order Management: Cart-to-order conversion, payment processing, expiration
  5. Promotion Code System: Code validation, discount application, usage tracking
  6. PDF Generation: QR code uniqueness, ticket format
  7. Stripe Integration: Payment processing, invoice generation
  8. Background Jobs: Async processing, error handling, retry logic
  9. API Endpoints: RESTful API functionality and authentication
  10. Dashboard Metrics: Query accuracy, performance

Seed Data Structure

# Creates comprehensive test data
users = User.create!([...])
events = Event.create!([...])
ticket_types = TicketType.create!([...])
promotion_codes = PromotionCode.create!([...])
orders = Order.create!([...])

🛠️ Available Development Tools

AST-Grep for Mass Code Replacement

The system has ast-grep installed for structural code search and replacement. This tool is particularly useful for:

  • Mass refactoring: Rename methods, classes, or variables across the codebase
  • Pattern-based replacements: Update code patterns using AST matching
  • Language-aware transformations: Safer than regex for code modifications

Usage Examples:

# Find all method calls to a specific method
ast-grep --pattern 'find_by_$FIELD($VALUE)' --lang ruby

# Replace method calls with new syntax
ast-grep --pattern 'find_by_$FIELD($VALUE)' --rewrite 'find_by($FIELD: $VALUE)' --lang ruby

# Search for specific Rails patterns
ast-grep --pattern 'validates :$FIELD, presence: true' --lang ruby

# Mass rename across multiple files
ast-grep --pattern 'old_method_name($$$ARGS)' --rewrite 'new_method_name($$$ARGS)' --lang ruby --update-all

# Find all order-related validations
ast-grep --pattern 'validates :status, inclusion: { in: \%w[...] }' --lang ruby

Best Practices:

  • Always run with --dry-run first to preview changes
  • Use --lang ruby for Ruby files to ensure proper AST parsing
  • Test changes in a branch before applying to main codebase
  • Particularly useful for Rails conventions and ActiveRecord pattern updates

Modern Rails 8 Stack

  • Solid Queue: Background job processing
  • Solid Cache: Fast caching layer
  • Solid Cable: Action Cable over Redis
  • Propshaft: Asset pipeline
  • Kamal: Deployment tooling
  • Thruster: Performance optimization

📝 Code Style & Conventions

  • Ruby Style: Follow Rails conventions and Rubocop rules
  • Database: Use Rails migrations for all schema changes
  • JavaScript: Stimulus controllers for interactive behavior
  • CSS: Tailwind utility classes with custom components
  • Service Layer: Complex business logic in service objects
  • Background Jobs: Async processing for long-running tasks
  • API Design: RESTful principles with versioning
  • Documentation: Inline comments for complex business logic
  • Mass Changes: Use ast-grep for structural code replacements instead of simple find/replace
  • Testing: Comprehensive test coverage for all business logic

This architecture provides a solid foundation for a scalable ticket selling platform with proper separation of concerns, security, and user experience, featuring modern Rails 8 capabilities and a comprehensive order management system.