# Backend Checkout Handling Improvements Based on your current Stripe integration, here are key improvements for robust checkout handling: ## 1. Enhanced Inventory Management with Concurrency Protection The current implementation doesn't prevent overselling during concurrent purchases. Add database-level concurrency protection: ```ruby # app/controllers/events_controller.rb def checkout cart_data = JSON.parse(params[:cart] || "{}") if cart_data.empty? redirect_to event_path(@event.slug, @event), alert: "Veuillez sélectionner au moins un billet" return end # Use transaction with row-level locking for inventory protection ActiveRecord::Base.transaction do line_items = [] order_items = [] cart_data.each do |ticket_type_id, item| # Lock the ticket type row to prevent race conditions ticket_type = @event.ticket_types.lock.find_by(id: ticket_type_id) next unless ticket_type quantity = item["quantity"].to_i next if quantity <= 0 # Check real-time availability with locked row sold_count = ticket_type.tickets.count available = ticket_type.quantity - sold_count if quantity > available redirect_to event_path(@event.slug, @event), alert: "Plus que #{available} billets disponibles pour #{ticket_type.name}" return end # Create line items and order data line_items << { price_data: { currency: "eur", product_data: { name: "#{@event.name} - #{ticket_type.name}", description: ticket_type.description }, unit_amount: ticket_type.price_cents }, quantity: quantity } order_items << { ticket_type_id: ticket_type.id, ticket_type_name: ticket_type.name, quantity: quantity, price_cents: ticket_type.price_cents } end if order_items.empty? redirect_to event_path(@event.slug, @event), alert: "Commande invalide" return end # Create Stripe session only after inventory validation session = Stripe::Checkout::Session.create({ payment_method_types: ["card"], line_items: line_items, mode: "payment", success_url: payment_success_url(event_id: @event.id, session_id: "{CHECKOUT_SESSION_ID}"), cancel_url: event_url(@event.slug, @event), customer_email: current_user.email, metadata: { event_id: @event.id, user_id: current_user.id, order_items: order_items.to_json } }) redirect_to session.url, allow_other_host: true end rescue ActiveRecord::RecordNotFound redirect_to event_path(@event.slug, @event), alert: "Type de billet introuvable" rescue Stripe::StripeError => e redirect_to event_path(@event.slug, @event), alert: "Erreur de traitement du paiement : #{e.message}" end ``` ## 2. Webhook Handler for Reliable Payment Confirmation Create a dedicated webhook endpoint for more reliable payment processing: ### Routes Configuration ```ruby # config/routes.rb post '/webhooks/stripe', to: 'webhooks#stripe' ``` ### Webhooks Controller ```ruby # app/controllers/webhooks_controller.rb class WebhooksController < ApplicationController skip_before_action :verify_authenticity_token before_action :verify_stripe_signature def stripe case @event.type when 'checkout.session.completed' handle_successful_payment(@event.data.object) when 'payment_intent.payment_failed' handle_failed_payment(@event.data.object) end head :ok end private def handle_successful_payment(session) # Process ticket creation in background job for reliability CreateTicketsJob.perform_later(session.id) end def handle_failed_payment(session) Rails.logger.error "Payment failed for session: #{session.id}" # Add any additional handling for failed payments end def verify_stripe_signature payload = request.body.read sig_header = request.env['HTTP_STRIPE_SIGNATURE'] begin @event = Stripe::Webhook.construct_event( payload, sig_header, ENV['STRIPE_WEBHOOK_SECRET'] ) rescue JSON::ParserError, Stripe::SignatureVerificationError => e Rails.logger.error "Stripe webhook signature verification failed: #{e.message}" head :bad_request end end end ``` ## 3. Background Job for Ticket Creation Use background jobs to prevent timeouts and improve reliability: ```ruby # app/jobs/create_tickets_job.rb class CreateTicketsJob < ApplicationJob queue_as :default retry_on StandardError, wait: :exponentially_longer, attempts: 5 def perform(session_id) session = Stripe::Checkout::Session.retrieve(session_id) return unless session.payment_status == 'paid' # Prevent duplicate processing return if Ticket.exists?(stripe_session_id: session_id) order_items = JSON.parse(session.metadata['order_items']) user = User.find(session.metadata['user_id']) event = Event.find(session.metadata['event_id']) ActiveRecord::Base.transaction do order_items.each do |item| ticket_type = TicketType.find(item['ticket_type_id']) item['quantity'].times do ticket = Ticket.create!( user: user, ticket_type: ticket_type, status: 'active', stripe_session_id: session_id, # Prevent duplicates price_cents: item['price_cents'] # Store historical price ) # Send email asynchronously TicketMailer.purchase_confirmation(ticket).deliver_later end end end end end ``` ## 4. Enhanced Error Handling & Recovery in Payment Success Improve the payment success handler with better error recovery: ```ruby # app/controllers/events_controller.rb - Enhanced payment_success method def payment_success session_id = params[:session_id] event_id = params[:event_id] # Validate parameters unless session_id.present? && event_id.present? redirect_to dashboard_path, alert: "Paramètres de confirmation manquants" return end begin @tickets = Ticket.includes(:ticket_type, :event) .where(stripe_session_id: session_id, user: current_user) if @tickets.any? # Tickets already created (webhook processed first) @event = @tickets.first.event render 'payment_success' else # Fallback: create tickets synchronously if webhook failed session = Stripe::Checkout::Session.retrieve(session_id) if session.payment_status == 'paid' CreateTicketsJob.perform_now(session_id) redirect_to payment_success_path(session_id: session_id, event_id: event_id) else redirect_to dashboard_path, alert: "Le paiement n'est pas encore confirmé" end end rescue Stripe::StripeError => e logger.error "Stripe error in payment_success: #{e.message}" redirect_to dashboard_path, alert: "Erreur de confirmation de paiement" rescue => e logger.error "Unexpected error in payment_success: #{e.message}" redirect_to dashboard_path, alert: "Une erreur inattendue s'est produite" end end ``` ## 5. Database Schema Improvements Add migration for better payment tracking: ```ruby # db/migrate/xxx_add_payment_tracking_to_tickets.rb class AddPaymentTrackingToTickets < ActiveRecord::Migration[7.0] def change add_column :tickets, :stripe_session_id, :string add_column :tickets, :purchased_at, :timestamp, default: -> { 'CURRENT_TIMESTAMP' } add_index :tickets, :stripe_session_id, unique: true add_index :tickets, [:user_id, :purchased_at] end end ``` ## 6. Security Considerations 1. **Rate Limiting**: Add rate limiting to checkout endpoints 2. **CSRF Protection**: Already implemented ✅ 3. **Input Validation**: Validate all cart data thoroughly 4. **Audit Logging**: Log all payment attempts and outcomes 5. **PCI Compliance**: Never store card data (Stripe handles this) ✅ ## 7. Monitoring & Observability Add metrics tracking to monitor checkout performance: ```ruby # Add to ApplicationController or EventsController around_action :track_checkout_metrics, only: [:checkout] private def track_checkout_metrics start_time = Time.current begin yield # Log successful checkout Rails.logger.info("Checkout completed", { event_id: @event&.id, user_id: current_user&.id, duration: Time.current - start_time }) rescue => e # Log failed checkout Rails.logger.error("Checkout failed", { event_id: @event&.id, user_id: current_user&.id, error: e.message, duration: Time.current - start_time }) raise end end ``` ## Summary of Improvements Your ticket checkout system is already well-implemented with Stripe integration! The enhancements above will make it production-ready: ### Critical Improvements 1. Add database row locking to prevent overselling during concurrent purchases 2. Implement Stripe webhooks for reliable payment processing 3. Use background jobs for ticket creation to prevent timeouts 4. Add duplicate prevention with stripe_session_id tracking ### Security & Reliability 5. Enhanced error recovery with fallback ticket creation 6. Comprehensive logging for debugging and monitoring 7. Database schema improvements for better payment tracking ### Key Files to Modify - `app/controllers/events_controller.rb` - Add inventory locking - `app/controllers/webhooks_controller.rb` - New webhook handler - `app/jobs/create_tickets_job.rb` - Background ticket creation - Migration for `stripe_session_id` field These enhancements will make your checkout system robust for high-traffic scenarios and edge cases.