develop #3

Merged
kbe merged 227 commits from develop into main 2025-09-16 14:35:23 +00:00
Showing only changes of commit 0b58768a24 - Show all commits

322
docs/checkout-handle.md Executable file
View File

@@ -0,0 +1,322 @@
# 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.