# Handle order management and checkout process # # This controller manages the order lifecycle from checkout to payment completion # Orders group multiple tickets together for better transaction management class OrdersController < ApplicationController before_action :authenticate_user! before_action :set_order, only: [:show, :checkout, :retry_payment, :increment_payment_attempt] before_action :set_event, only: [:new, :create] # Display new order form with name collection # # On this page user can see order summary and complete the tickets details # (first name and last name) for each ticket ordered def new @cart_data = session[:pending_cart] || {} if @cart_data.empty? redirect_to event_path(@event.slug, @event), alert: "Veuillez d'abord sélectionner vos billets sur la page de l'événement" return end # Build list of tickets requiring names @tickets_needing_names = [] @cart_data.each do |ticket_type_id, item| ticket_type = @event.ticket_types.find_by(id: ticket_type_id) next unless ticket_type quantity = item["quantity"].to_i next if quantity <= 0 quantity.times do |i| @tickets_needing_names << { ticket_type_id: ticket_type.id, ticket_type_name: ticket_type.name, ticket_type_price: ticket_type.price_cents, index: i } end end end # Create a new order with tickets # # Here a new order is created with associated tickets in draft state. # When user is ready they can proceed to payment via the order checkout def create @cart_data = session[:pending_cart] || {} if @cart_data.empty? redirect_to event_path(@event.slug, @event), alert: "Aucun billet sélectionné" return end success = false ActiveRecord::Base.transaction do @order = current_user.orders.create!(event: @event, status: "draft") order_params[:tickets_attributes]&.each do |index, ticket_attrs| next if ticket_attrs[:first_name].blank? || ticket_attrs[:last_name].blank? ticket_type = @event.ticket_types.find(ticket_attrs[:ticket_type_id]) ticket = @order.tickets.build( ticket_type: ticket_type, first_name: ticket_attrs[:first_name], last_name: ticket_attrs[:last_name], status: "draft" ) unless ticket.save flash[:alert] = "Erreur lors de la création des billets: #{ticket.errors.full_messages.join(', ')}" raise ActiveRecord::Rollback end end if @order.tickets.present? @order.calculate_total! success = true else flash[:alert] = "Aucun billet valide créé" raise ActiveRecord::Rollback end end # Handle redirects outside transaction if success session[:draft_order_id] = @order.id session.delete(:pending_cart) redirect_to checkout_order_path(@order) else redirect_to event_order_new_path(@event.slug, @event.id) end rescue => e error_message = e.message.present? ? e.message : "Erreur inconnue" flash[:alert] = "Une erreur est survenue: #{error_message}" redirect_to event_order_new_path(@event.slug, @event.id) end # Display order summary # # def show @tickets = @order.tickets.includes(:ticket_type) end # Display payment page for an order # # Display a summary of all tickets in the order and permit user # to proceed to payment via Stripe def checkout # Handle expired orders if @order.expired? @order.expire_if_overdue! return redirect_to event_path(@order.event.slug, @order.event), alert: "Votre commande a expiré. Veuillez recommencer." end @tickets = @order.tickets.includes(:ticket_type) @total_amount = @order.total_amount_cents @expiring_soon = @order.expiring_soon? # Create Stripe checkout session if Stripe is configured if Rails.application.config.stripe[:secret_key].present? begin @checkout_session = create_stripe_session rescue => e error_message = e.message.present? ? e.message : "Erreur Stripe inconnue" Rails.logger.error "Stripe checkout session creation failed: #{error_message}" flash[:alert] = "Erreur lors de la création de la session de paiement" end end end # Increment payment attempt - called via AJAX when user clicks pay button def increment_payment_attempt @order.increment_payment_attempt! render json: { success: true, attempts: @order.payment_attempts } end # Allow users to retry payment for failed/cancelled payments def retry_payment unless @order.can_retry_payment? redirect_to event_path(@order.event.slug, @order.event), alert: "Cette commande ne peut plus être payée" return end redirect_to order_checkout_path(@order) end # Handle successful payment def payment_success session_id = params[:session_id] # Check if Stripe is properly configured stripe_configured = Rails.application.config.stripe[:secret_key].present? Rails.logger.debug "Payment success - Stripe configured: #{stripe_configured}" unless stripe_configured redirect_to dashboard_path, alert: "Le système de paiement n'est pas correctement configuré. Veuillez contacter l'administrateur." return end begin stripe_session = Stripe::Checkout::Session.retrieve(session_id) if stripe_session.payment_status == "paid" # Get order_id from session metadata order_id = stripe_session.metadata["order_id"] unless order_id.present? redirect_to dashboard_path, alert: "Informations de commande manquantes" return end # Find and update the order @order = current_user.orders.includes(tickets: :ticket_type).find(order_id) @order.mark_as_paid! # Send confirmation emails @order.tickets.each do |ticket| begin TicketMailer.purchase_confirmation(ticket).deliver_now rescue => e Rails.logger.error "Failed to send confirmation email for ticket #{ticket.id}: #{e.message}" # Don't fail the entire payment process due to email/PDF generation issues end end # Clear session data session.delete(:pending_cart) session.delete(:ticket_names) session.delete(:draft_order_id) render "payment_success" else redirect_to dashboard_path, alert: "Le paiement n'a pas été complété avec succès" end rescue Stripe::StripeError => e error_message = e.message.present? ? e.message : "Erreur Stripe inconnue" redirect_to dashboard_path, alert: "Erreur lors du traitement de votre confirmation de paiement : #{error_message}" rescue => e error_message = e.message.present? ? e.message : "Erreur inconnue" Rails.logger.error "Payment success error: #{e.class} - #{error_message}" redirect_to dashboard_path, alert: "Une erreur inattendue s'est produite : #{error_message}" end end # Handle payment failure/cancellation def payment_cancel order_id = session[:draft_order_id] if order_id.present? order = current_user.orders.find_by(id: order_id, status: "draft") if order&.can_retry_payment? redirect_to order_checkout_path(order), alert: "Le paiement a été annulé. Vous pouvez réessayer." else session.delete(:draft_order_id) redirect_to dashboard_path, alert: "Le paiement a été annulé et votre commande a expiré." end else redirect_to dashboard_path, alert: "Le paiement a été annulé" end end private def set_order @order = current_user.orders.includes(:tickets, :event).find(params[:id]) rescue ActiveRecord::RecordNotFound redirect_to dashboard_path, alert: "Commande non trouvée" end def set_event @event = Event.includes(:ticket_types).find(params[:id]) rescue ActiveRecord::RecordNotFound redirect_to events_path, alert: "Événement non trouvé" end def order_params params.permit(tickets_attributes: [ :ticket_type_id, :first_name, :last_name ]) end def create_stripe_session line_items = @order.tickets.map do |ticket| { price_data: { currency: "eur", product_data: { name: "#{@order.event.name} - #{ticket.ticket_type.name}", description: ticket.ticket_type.description }, unit_amount: ticket.price_cents }, quantity: 1 } end Stripe::Checkout::Session.create( payment_method_types: ["card"], line_items: line_items, mode: "payment", success_url: order_payment_success_url + "?session_id={CHECKOUT_SESSION_ID}", cancel_url: order_payment_cancel_url, metadata: { order_id: @order.id, user_id: current_user.id } ) end end