Stripe does not support negative invoices, so to allow correct invoice generation, we apply dismiss negative invoices.
19 KiB
Executable File
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_professionnalfield for event promoters with enhanced permissions - Onboarding System: Multi-step onboarding process with
onboarding_completedtracking - Stripe Integration:
stripe_customer_idfor 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_outwith 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
- Cart-to-Order Conversion: Convert shopping cart to draft order with 15-minute expiration
- Platform Fee Calculation: Automatic calculation of €0.50 fixed + 1.5% per ticket
- Promotion Code Application: Real-time discount validation and application
- Stripe Checkout Session: Create payment session with order metadata
- 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
- Order Status Update: Transition from pending_payment to paid
- Ticket Generation: Create tickets associated with the order
- Stripe Invoice Creation: Async invoice generation for accounting
- 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
- User Authentication: Registration, login, logout flows
- Professional User Onboarding: Multi-step onboarding process
- Event Creation: Validation, state management, relationships
- Order Management: Cart-to-order conversion, payment processing, expiration
- Promotion Code System: Code validation, discount application, usage tracking
- PDF Generation: QR code uniqueness, ticket format
- Stripe Integration: Payment processing, invoice generation
- Background Jobs: Async processing, error handling, retry logic
- API Endpoints: RESTful API functionality and authentication
- 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-runfirst to preview changes - Use
--lang rubyfor 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-grepfor 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.