# API controller for order management # Provides RESTful endpoints for order operations module Api module V1 class OrdersController < ApiController before_action :authenticate_user! before_action :set_order, only: [ :show, :checkout, :retry_payment, :increment_payment_attempt ] before_action :set_event, only: [ :new, :create ] # Skip API key authentication for increment_payment_attempt action (used by frontend forms) skip_before_action :authenticate_api_key, only: [ :increment_payment_attempt ] # GET /api/v1/orders/new # Returns data needed for new order form def new cart_data = params[:cart_data] || session[:pending_cart] || {} if cart_data.empty? render json: { error: "Veuillez d'abord sélectionner vos billets sur la page de l'événement" }, status: :bad_request return end 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 render json: { tickets_needing_names: tickets_needing_names }, status: :ok end # POST /api/v1/orders # Creates a new order with tickets def create cart_data = params[:cart_data] || session[:pending_cart] || {} if cart_data.empty? render json: { error: "Aucun billet sélectionné" }, status: :bad_request 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 render json: { error: "Erreur lors de la création des billets: #{ticket.errors.full_messages.join(', ')}" }, status: :unprocessable_entity raise ActiveRecord::Rollback end end if @order.tickets.present? @order.calculate_total! success = true else render json: { error: "Aucun billet valide créé" }, status: :unprocessable_entity raise ActiveRecord::Rollback end end if success session[:draft_order_id] = @order.id session.delete(:pending_cart) render json: { order: @order, redirect_to: checkout_order_path(@order) }, status: :created end rescue => e error_message = e.message.present? ? e.message : "Erreur inconnue" render json: { error: "Une erreur est survenue: #{error_message}" }, status: :internal_server_error end # GET /api/v1/orders/:id # Returns order summary def show tickets = @order.tickets.includes(:ticket_type) render json: { order: @order, tickets: tickets }, status: :ok end # GET /api/v1/orders/:id/checkout # Returns checkout data for an order def checkout if @order.expired? @order.expire_if_overdue! render json: { error: "Votre commande a expiré. Veuillez recommencer." }, status: :gone return end tickets = @order.tickets.includes(:ticket_type) total_amount = @order.total_amount_cents expiring_soon = @order.expiring_soon? checkout_session = nil 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}" render json: { error: "Erreur lors de la création de la session de paiement" }, status: :internal_server_error return end end render json: { order: @order, tickets: tickets, total_amount: total_amount, expiring_soon: expiring_soon, checkout_session: checkout_session }, status: :ok end # PATCH /api/v1/orders/:id/increment_payment_attempt # Increments payment attempt counter def increment_payment_attempt @order.increment_payment_attempt! render json: { success: true, attempts: @order.payment_attempts }, status: :ok end # POST /api/v1/orders/:id/retry_payment # Allows retrying payment for failed orders def retry_payment unless @order.can_retry_payment? render json: { error: "Cette commande ne peut plus être payée" }, status: :forbidden return end render json: { redirect_to: checkout_order_path(@order) }, status: :ok end # GET /api/v1/orders/payment_success # Handles successful payment confirmation def payment_success session_id = params[:session_id] stripe_configured = Rails.application.config.stripe[:secret_key].present? Rails.logger.debug "Payment success - Stripe configured: #{stripe_configured}" unless stripe_configured render json: { error: "Le système de paiement n'est pas correctement configuré. Veuillez contacter l'administrateur." }, status: :service_unavailable return end begin stripe_session = Stripe::Checkout::Session.retrieve(session_id) if stripe_session.payment_status == "paid" order_id = stripe_session.metadata["order_id"] unless order_id.present? render json: { error: "Informations de commande manquantes" }, status: :bad_request return end @order = current_user.orders.includes(tickets: :ticket_type).find(order_id) @order.mark_as_paid! begin StripeInvoiceGenerationJob.perform_later(@order.id) Rails.logger.info "Scheduled Stripe invoice generation for order #{@order.id}" rescue => e Rails.logger.error "Failed to schedule invoice generation for order #{@order.id}: #{e.message}" end @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}" end end session.delete(:pending_cart) session.delete(:ticket_names) session.delete(:draft_order_id) render json: { order: @order, tickets: @order.tickets }, status: :ok else render json: { error: "Le paiement n'a pas été complété avec succès" }, status: :payment_required end rescue Stripe::StripeError => e error_message = e.message.present? ? e.message : "Erreur Stripe inconnue" render json: { error: "Erreur lors du traitement de votre confirmation de paiement : #{error_message}" }, status: :bad_request rescue => e error_message = e.message.present? ? e.message : "Erreur inconnue" Rails.logger.error "Payment success error: #{e.class} - #{error_message}" render json: { error: "Une erreur inattendue s'est produite : #{error_message}" }, status: :internal_server_error end end # POST /api/v1/orders/payment_cancel # Handles payment cancellation def payment_cancel order_id = params[: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? render json: { message: "Le paiement a été annulé. Vous pouvez réessayer.", redirect_to: checkout_order_path(order) }, status: :ok else session.delete(:draft_order_id) render json: { message: "Le paiement a été annulé et votre commande a expiré." }, status: :gone end else render json: { message: "Le paiement a été annulé" }, status: :ok end end private def set_order @order = current_user.orders.includes(:tickets, :event).find(params[:id]) rescue ActiveRecord::RecordNotFound render json: { error: "Commande non trouvée" }, status: :not_found end def set_event @event = Event.includes(:ticket_types).find(params[:id]) rescue ActiveRecord::RecordNotFound render json: { error: "Événement non trouvé" }, status: :not_found 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 end end