Add payment session expiration and logging

The main changes in this commit are: - Adding 30-minute expiration to
Stripe checkout sessions - Adding comprehensive logging throughout
payment flow - Improving payment failure and expiration handling -
Moving payment processing to static methods - Refactoring payment
session creation and handling - Adding better error handling for Stripe
operations

``` Add payment session expiration and logging ```
apikeys
Razmig Sarkissian 2 months ago
parent 84a7047053
commit 31b87cea2f
  1. 531
      tournaments/services/payment_service.py
  2. 34
      tournaments/services/tournament_registration.py
  3. 90
      tournaments/views.py

@ -5,12 +5,11 @@ from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
import stripe
from datetime import datetime, timedelta
from ..models import TeamRegistration, PlayerRegistration, Tournament
from ..models.player_registration import PlayerPaymentType
from .email_service import TournamentEmailService
from .tournament_registration import RegistrationCartManager
from ..utils.extensions import is_not_sqlite_backend
from tournaments.services.currency_service import CurrencyService
class PaymentService:
@ -22,51 +21,101 @@ class PaymentService:
self.request = request
self.stripe_api_key = settings.STRIPE_SECRET_KEY
def create_checkout_session(self, tournament_id, team_fee, cart_data=None, team_registration_id=None):
def create_checkout_session(self, tournament_id, team_fee, cart_manager=None, team_registration_id=None):
"""
Create a Stripe checkout session for tournament payment
"""
print(f"[TOURNAMENT PAYMENT] Creating checkout session")
print(f"[TOURNAMENT PAYMENT] Tournament ID: {tournament_id}")
print(f"[TOURNAMENT PAYMENT] Team fee: {team_fee}")
print(f"[TOURNAMENT PAYMENT] Cart manager: {cart_manager}")
print(f"[TOURNAMENT PAYMENT] Team registration ID: {team_registration_id}")
print(f"[TOURNAMENT PAYMENT] User: {self.request.user}")
session_duration_minutes = 30
expires_at = int((datetime.now() + timedelta(minutes=session_duration_minutes)).timestamp())
stripe.api_key = self.stripe_api_key
tournament = get_object_or_404(Tournament, id=tournament_id)
user = self.request.user if self.request.user.is_authenticated else None
customer_email = user.email if user else None
customer_name = f"{user.first_name} {user.last_name}".strip() if user and user.first_name else None
registration_type = 'direct'
if not team_registration_id:
registration_type = 'cart'
print(f"[TOURNAMENT PAYMENT] Tournament: {tournament.name}")
print(f"[TOURNAMENT PAYMENT] Customer email: {customer_email}")
print(f"[TOURNAMENT PAYMENT] Customer name: {customer_name}")
print(f"[TOURNAMENT PAYMENT] Registration type: {registration_type}")
currency_service = CurrencyService()
# Check if payments are enabled for this tournament
if not tournament.should_request_payment():
print(f"[TOURNAMENT PAYMENT] Payments not enabled for tournament")
raise Exception("Les paiements ne sont pas activés pour ce tournoi.")
# Get currency code and validate it
currency_code = tournament.currency_code or 'EUR'
if not currency_service.validate_currency_code(currency_code):
print(f"[TOURNAMENT PAYMENT] Invalid currency code: {currency_code}")
raise Exception(f"Devise non supportée: {currency_code}")
print(f"[TOURNAMENT PAYMENT] Currency code: {currency_code}")
# Convert amount to Stripe format
stripe_amount = currency_service.convert_to_stripe_amount(team_fee, currency_code)
print(f"[TOURNAMENT PAYMENT] Stripe amount: {stripe_amount}")
# Get user email if authenticated
customer_email = self.request.user.email if self.request.user.is_authenticated else None
# Determine the appropriate cancel URL based on the context
if team_registration_id:
if not team_registration_id and cart_manager:
team_registration = self._process_pre_registration_payment(cart_manager)
if not team_registration:
print(f"[TOURNAMENT PAYMENT] Failed to create team registration")
raise Exception("Erreur lors de la création de la réservation")
team_registration_id = team_registration.id
print(f"[TOURNAMENT PAYMENT] Created team registration: {team_registration_id}")
if not team_registration_id:
print(f"[TOURNAMENT PAYMENT] No team registration ID available")
raise Exception("Erreur lors de la création de la réservation")
# Determine the appropriate cancel URL based on the context
if registration_type == 'direct':
# If we're paying for an existing registration, go back to tournament info
cancel_url = self.request.build_absolute_uri(
reverse('tournament-info', kwargs={'tournament_id': tournament_id})
)
else:
# If we're in the registration process, go back to registration form
self.request.session['cancel_team_registration_id'] = str(team_registration_id)
cancel_url = self.request.build_absolute_uri(
reverse('register_tournament', kwargs={'tournament_id': tournament_id})
reverse('register_tournament', kwargs={
'tournament_id': tournament_id,
})
)
print(f"[TOURNAMENT PAYMENT] Cancel URL: {cancel_url}")
base_metadata = {
'tournament_id': str(tournament_id),
'team_registration_id': str(team_registration_id),
'customer_name': customer_name or 'Non fourni',
'customer_email': customer_email or 'Non fourni',
'user_id': str(self.request.user.id) if self.request.user.is_authenticated else None,
'payment_source': 'tournament', # Identify payment source
'registration_type': registration_type,
'source_page': 'tournament_info' if team_registration_id else 'register_tournament',
'currency_code': currency_code, # Store currency for later reference
}
print(f"[TOURNAMENT PAYMENT] Base metadata: {base_metadata}")
if tournament.is_corporate_tournament:
print(f"[TOURNAMENT PAYMENT] Corporate tournament - using direct payment")
# Corporate tournament metadata
metadata = {
**base_metadata,
@ -74,6 +123,7 @@ class PaymentService:
'stripe_account_type': 'direct'
}
else:
print(f"[TOURNAMENT PAYMENT] Regular tournament - using Stripe Connect")
# Regular tournament metadata
metadata = {
**base_metadata,
@ -82,19 +132,7 @@ class PaymentService:
'stripe_account_id': tournament.stripe_account_id
}
if cart_data:
metadata.update({
'registration_cart_id': str(cart_data['cart_id']),
'registration_type': 'cart',
'player_count': str(cart_data.get('player_count', 0)),
'waiting_list_position': str(cart_data.get('waiting_list_position', -1))
})
elif team_registration_id:
metadata.update({
'team_registration_id': str(team_registration_id),
'registration_type': 'direct'
})
self.request.session['team_registration_id'] = str(team_registration_id)
self.request.session['team_registration_id'] = str(team_registration_id)
metadata.update({
'tournament_name': tournament.broadcast_display_name(),
@ -103,11 +141,14 @@ class PaymentService:
'tournament_fee': str(team_fee)
})
print(f"[TOURNAMENT PAYMENT] Final metadata: {metadata}")
# Common checkout session parameters
if tournament.is_corporate_tournament:
# Direct charge without transfers when umpire is platform owner
checkout_session_params = {
'payment_method_types': ['card'],
'expires_at': expires_at,
'line_items': [{
'price_data': {
'currency': currency_code.lower(), # Use tournament currency
@ -126,28 +167,37 @@ class PaymentService:
'cancel_url': cancel_url,
'metadata': metadata
}
print(f"[TOURNAMENT PAYMENT] Corporate checkout params: {checkout_session_params}")
else:
# Get the umpire's Stripe account ID
stripe_account_id = tournament.stripe_account_id
if not stripe_account_id:
print(f"[TOURNAMENT PAYMENT] No Stripe account ID for umpire")
raise Exception("L'arbitre n'a pas configuré son compte Stripe.")
print(f"[TOURNAMENT PAYMENT] Umpire Stripe account: {stripe_account_id}")
# Calculate commission
commission_rate = tournament.event.creator.effective_commission_rate()
platform_amount = currency_service.convert_to_stripe_amount(
team_fee * commission_rate, currency_code
)
print(f"[TOURNAMENT PAYMENT] Commission rate: {commission_rate}")
print(f"[TOURNAMENT PAYMENT] Platform amount: {platform_amount}")
# Direct charge on connected account - this will appear in umpire's dashboard
checkout_session_params = {
'payment_method_types': ['card'],
'expires_at': expires_at,
'line_items': [{
'price_data': {
'currency': currency_code.lower(), # Use tournament currency
'currency': currency_code.lower(),
'product_data': {
'name': f'{tournament.broadcast_display_name()} du {tournament.formatted_start_date()}',
'description': f'Lieu {tournament.event.club.name}',
},
'unit_amount': stripe_amount, # Amount in proper currency format
'unit_amount': stripe_amount,
},
'quantity': 1,
}],
@ -157,133 +207,100 @@ class PaymentService:
),
'cancel_url': cancel_url,
'payment_intent_data': {
'application_fee_amount': platform_amount,
'transfer_data': {
'destination': stripe_account_id,
},
'application_fee_amount': platform_amount, # Your commission
'description': f'Inscription {tournament.broadcast_display_name()} du {tournament.formatted_start_date()}',
'metadata': metadata # All metadata will be visible to umpire
},
'metadata': metadata
}
'metadata': metadata # Session metadata
}
print(f"[TOURNAMENT PAYMENT] Direct charge checkout params: {checkout_session_params}")
# Add customer_email if available
if customer_email:
checkout_session_params['customer_email'] = customer_email
print(f"[TOURNAMENT PAYMENT] Added customer email to params")
# Create the checkout session
try:
checkout_session = stripe.checkout.Session.create(**checkout_session_params)
print(f"[TOURNAMENT PAYMENT] Creating Stripe checkout session...")
if tournament.is_corporate_tournament:
# Create on platform account
checkout_session = stripe.checkout.Session.create(**checkout_session_params)
else:
# Create on connected account (direct charge)
stripe_account_id = tournament.stripe_account_id
self.request.session['stripe_account_id'] = stripe_account_id if not tournament.is_corporate_tournament else None
checkout_session = stripe.checkout.Session.create(
**checkout_session_params,
stripe_account=stripe_account_id # This creates the session on the connected account
)
# Store checkout session ID and source page in session
self.request.session['stripe_checkout_session_id'] = checkout_session.id
self.request.session['payment_source_page'] = 'tournament_info' if team_registration_id else 'register_tournament'
self.request.session.modified = True
print(f"[TOURNAMENT PAYMENT] Checkout session created successfully: {checkout_session.id}")
print(f"[TOURNAMENT PAYMENT] Stored session data - checkout_session_id: {checkout_session.id}")
return checkout_session
except stripe.error.StripeError as e:
print(f"[TOURNAMENT PAYMENT] Stripe error: {str(e)}")
# Handle specific Stripe errors more gracefully
if 'destination' in str(e):
raise Exception("Erreur avec le compte Stripe de l'arbitre. Contactez l'administrateur.")
else:
raise Exception(f"Erreur Stripe: {str(e)}")
def process_successful_payment(self, tournament_id, checkout_session):
def process_successful_payment(self, checkout_session):
"""
Process a successful Stripe payment
Returns a tuple (success, redirect_response)
"""
print(f"Processing payment for tournament {tournament_id}")
tournament = get_object_or_404(Tournament, id=tournament_id)
# Check if this is a payment for an existing team registration
team_registration_id = self.request.session.get('team_registration_id')
print(f"Team registration ID from session: {team_registration_id}")
# Track payment statuses for debugging
payment_statuses = []
if team_registration_id:
success = self._process_direct_payment(checkout_session)
payment_statuses.append(success)
print(f"Direct payment processing result: {success}")
else:
# This is a payment during registration process
success = self._process_registration_payment(tournament, checkout_session)
payment_statuses.append(success)
print(f"Registration payment processing result: {success}")
success = PaymentService.process_direct_payment(checkout_session)
# Print combined payment status
print(f"Payment statuses: {payment_statuses}")
print(any(payment_statuses))
print(f"process_direct_payment: {success}")
# Clear checkout session ID
if 'stripe_checkout_session_id' in self.request.session:
del self.request.session['stripe_checkout_session_id']
return any(payment_statuses)
def _process_direct_payment(self, checkout_session):
"""Process payment for an existing team registration"""
team_registration_id = self.request.session.get('team_registration_id')
if not team_registration_id:
print("No team registration ID found in session")
return False
try:
print(f"Looking for team registration with ID: {team_registration_id}")
team_registration = TeamRegistration.objects.get(id=team_registration_id)
success = self._update_registration_payment_info(
team_registration,
checkout_session.payment_intent
)
# Clean up session
if 'team_registration_id' in self.request.session:
del self.request.session['team_registration_id']
return success
if success:
TournamentEmailService.send_payment_confirmation(team_registration, checkout_session.payment_intent)
return success
except TeamRegistration.DoesNotExist:
print(f"Team registration not found with ID: {team_registration_id}")
return False
except Exception as e:
print(f"Error in _process_direct_payment: {str(e)}")
return False
def _process_registration_payment(self, tournament, checkout_session):
def _process_pre_registration_payment(self, cart_manager):
"""Process payment made during registration"""
cart_manager = RegistrationCartManager(self.request)
cart_data = cart_manager.get_cart_data()
# Checkout and create registration
success, result = cart_manager.checkout()
success, result = cart_manager.checkout(confirmed=False)
if not success:
return False
return None
# Process payment for the new registration
team_registration = result # result is team_registration object
self._update_registration_payment_info(
team_registration,
checkout_session.payment_intent
)
# Send confirmation email if appropriate
waiting_list_position = cart_data.get('waiting_list_position', -1)
if is_not_sqlite_backend():
email_service = TournamentEmailService()
email_service.send_registration_confirmation(
self.request,
tournament,
team_registration,
waiting_list_position
)
return True
def _update_registration_payment_info(self, team_registration, payment_intent_id):
"""Update player registrations with payment information"""
team_registration.confirm_registration(payment_intent_id)
return True
team_registration.confirm_pre_registration()
# self._update_registration_payment_info(
# team_registration,
# checkout_session.payment_intent
# )
# # Send confirmation email if appropriate
# waiting_list_position = cart_data.get('waiting_list_position', -1)
# if is_not_sqlite_backend():
# email_service = TournamentEmailService()
# email_service.send_registration_confirmation(
# self.request,
# tournament,
# team_registration,
# waiting_list_position
# )
return team_registration
# def _update_registration_payment_info(self, team_registration, payment_intent_id):
# """Update player registrations with payment information"""
# team_registration.confirm_registration(payment_intent_id)
# return True
def process_refund(self, team_registration_id, force_refund=False):
"""
@ -314,25 +331,52 @@ class PaymentService:
if not payment_id:
return False, "Aucun paiement trouvé pour cette équipe.", None
# Get the Stripe payment intent
payment_intent = stripe.PaymentIntent.retrieve(payment_id)
# Get the Stripe payment intent from the correct account
try:
if tournament.is_corporate_tournament:
# Corporate tournament - payment on platform account
payment_intent = stripe.PaymentIntent.retrieve(payment_id)
else:
# Regular tournament - payment on connected account
if not tournament.stripe_account_id:
return False, "Compte Stripe de l'arbitre non configuré.", None
#legacy retrieval
try:
payment_intent = stripe.PaymentIntent.retrieve(payment_id)
except:
payment_intent = stripe.PaymentIntent.retrieve(
payment_id,
stripe_account=tournament.stripe_account_id
)
except stripe.error.InvalidRequestError as e:
return False, f"Paiement introuvable: {str(e)}", None
if payment_intent.status != 'succeeded':
return False, "Le paiement n'a pas été complété, il ne peut pas être remboursé.", None
# Process the refund - with different parameters based on tournament type
refund_params = {
'payment_intent': payment_id
}
# Only include transfer reversal for non-corporate tournaments
if not tournament.is_corporate_tournament:
refund_params.update({
'refund_application_fee': True,
'reverse_transfer': True
})
if tournament.is_corporate_tournament:
# Corporate tournament refund (simple refund)
refund = stripe.Refund.create(
payment_intent=payment_id
)
else:
# Direct charge refund (on connected account)
# Check if there's an application fee to refund
refund_params = {
'payment_intent': payment_id,
'stripe_account': tournament.stripe_account_id
}
# Only add refund_application_fee if there's actually an application fee
if hasattr(payment_intent, 'application_fee_amount') and payment_intent.application_fee_amount and payment_intent.application_fee_amount > 0:
refund_params['refund_application_fee'] = True
print(f"[REFUND] Refunding application fee of {payment_intent.application_fee_amount}")
else:
print(f"[REFUND] No application fee to refund")
refund = stripe.Refund.create(**refund_params)
refund = stripe.Refund.create(**refund_params)
for player_reg in player_registrations:
player_reg.payment_type = None
@ -349,6 +393,197 @@ class PaymentService:
except Exception as e:
return False, f"Erreur lors du remboursement: {str(e)}", None
@staticmethod
def process_failed_payment_intent(payment_intent):
"""Process a failed Payment Intent"""
if hasattr(payment_intent, 'metadata'):
metadata = payment_intent.metadata
elif isinstance(payment_intent, dict) and 'metadata' in payment_intent:
metadata = payment_intent['metadata']
else:
print("[FAILED PAYMENT] No metadata found in payment intent")
return False
print(f"[FAILED PAYMENT] Processing failed payment intent")
print(f"[FAILED PAYMENT] Metadata: {metadata}")
tournament_id = metadata.get('tournament_id')
team_registration_id = metadata.get('team_registration_id')
registration_type = metadata.get('registration_type')
print(f"[FAILED PAYMENT] Tournament ID: {tournament_id}")
print(f"[FAILED PAYMENT] Team Registration ID: {team_registration_id}")
print(f"[FAILED PAYMENT] Registration Type: {registration_type}")
# Release reserved spot for cart registrations
if registration_type == 'cart' and tournament_id:
try:
tournament = Tournament.objects.get(id=tournament_id)
old_spots = tournament.reserved_spots
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
tournament.save()
print(f"[FAILED PAYMENT] Released reserved spot: {old_spots}{tournament.reserved_spots}")
except Tournament.DoesNotExist:
print(f"[FAILED PAYMENT] Tournament {tournament_id} not found")
except Exception as e:
print(f"[FAILED PAYMENT] Error updating tournament reserved spots: {str(e)}")
# Clean up unpaid team registration
if not team_registration_id:
print("[FAILED PAYMENT] No team registration ID found in metadata")
return False
try:
print(f"[FAILED PAYMENT] Looking for team registration with ID: {team_registration_id}")
team_registration = TeamRegistration.objects.get(id=team_registration_id)
if not team_registration.is_paid():
team_registration.delete()
print(f"[FAILED PAYMENT] Deleted unpaid team registration {team_registration_id}")
else:
print(f"[FAILED PAYMENT] Team registration {team_registration_id} is already paid")
return True
except TeamRegistration.DoesNotExist:
print(f"[FAILED PAYMENT] Team registration {team_registration_id} not found")
return False
except Exception as e:
print(f"[FAILED PAYMENT] Error processing team registration: {str(e)}")
return False
@staticmethod
def process_expired_checkout_session(checkout_session):
"""Process an expired Checkout Session"""
if hasattr(checkout_session, 'metadata'):
metadata = checkout_session.metadata
elif isinstance(checkout_session, dict) and 'metadata' in checkout_session:
metadata = checkout_session['metadata']
else:
print("[EXPIRED SESSION] No metadata found in checkout session")
return False
print(f"[EXPIRED SESSION] Processing expired checkout session")
print(f"[EXPIRED SESSION] Metadata: {metadata}")
tournament_id = metadata.get('tournament_id')
team_registration_id = metadata.get('team_registration_id')
registration_type = metadata.get('registration_type')
print(f"[EXPIRED SESSION] Tournament ID: {tournament_id}")
print(f"[EXPIRED SESSION] Team Registration ID: {team_registration_id}")
print(f"[EXPIRED SESSION] Registration Type: {registration_type}")
# Release reserved spot for cart registrations
if registration_type == 'cart' and tournament_id:
try:
tournament = Tournament.objects.get(id=tournament_id)
old_spots = tournament.reserved_spots
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
tournament.save()
print(f"[EXPIRED SESSION] Released reserved spot: {old_spots}{tournament.reserved_spots}")
except Tournament.DoesNotExist:
print(f"[EXPIRED SESSION] Tournament {tournament_id} not found")
except Exception as e:
print(f"[EXPIRED SESSION] Error updating tournament reserved spots: {str(e)}")
# Clean up unpaid team registration
if not team_registration_id:
print("[EXPIRED SESSION] No team registration ID found in metadata")
return False
try:
print(f"[EXPIRED SESSION] Looking for team registration with ID: {team_registration_id}")
team_registration = TeamRegistration.objects.get(id=team_registration_id)
if not team_registration.is_paid():
team_registration.delete()
print(f"[EXPIRED SESSION] Deleted unpaid team registration {team_registration_id}")
else:
print(f"[EXPIRED SESSION] Team registration {team_registration_id} is already paid")
return True
except TeamRegistration.DoesNotExist:
print(f"[EXPIRED SESSION] Team registration {team_registration_id} not found")
return False
except Exception as e:
print(f"[EXPIRED SESSION] Error processing team registration: {str(e)}")
return False
@staticmethod
def process_failed_or_expired_session(stripe_object):
"""Legacy method for backward compatibility - delegates to specific handlers"""
# Try to determine object type and delegate
if hasattr(stripe_object, 'object'):
object_type = stripe_object.object
elif isinstance(stripe_object, dict) and 'object' in stripe_object:
object_type = stripe_object['object']
else:
print("[LEGACY HANDLER] Cannot determine stripe object type")
return False
print(f"[LEGACY HANDLER] Delegating {object_type} to specific handler")
if object_type == 'payment_intent':
return PaymentService.process_failed_payment_intent(stripe_object)
elif object_type == 'checkout.session':
return PaymentService.process_expired_checkout_session(stripe_object)
else:
print(f"[LEGACY HANDLER] Unknown object type: {object_type}")
return False
@staticmethod
def process_direct_payment(checkout_session):
"""Process payment for an existing team registration"""
metadata = checkout_session.metadata
tournament_id = metadata.get('tournament_id')
team_registration_id = metadata.get('team_registration_id')
registration_type = metadata.get('registration_type')
if tournament_id and registration_type == 'cart':
try:
tournament = Tournament.objects.get(id=tournament_id)
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
tournament.save()
print(f"Decreased reserved spots for tournament {tournament_id} after payment failure")
except Tournament.DoesNotExist:
print(f"Tournament not found with ID: {tournament_id}")
except Exception as e:
print(f"Error saving tournament for team registration: {str(e)}")
if not team_registration_id:
print("No team registration ID found in session")
return False
try:
print(f"Looking for team registration with ID: {team_registration_id}")
team_registration = TeamRegistration.objects.get(id=team_registration_id)
if tournament_id and registration_type == 'cart' and team_registration.tournament is None:
try:
tournament = Tournament.objects.get(id=tournament_id)
team_registration.tournament = tournament
team_registration.save()
print(f"Saved tournament for team registration {team_registration.id}")
except Tournament.DoesNotExist:
print(f"Tournament not found with ID: {tournament_id}")
except Exception as e:
print(f"Error saving tournament for team registration: {str(e)}")
if team_registration.is_paid():
return True
team_registration.confirm_registration(checkout_session.payment_intent)
TournamentEmailService.send_payment_confirmation(team_registration, checkout_session.payment_intent)
return True
except TeamRegistration.DoesNotExist:
print(f"Team registration not found with ID: {team_registration_id}")
except Exception as e:
print(f"Error in _process_direct_payment: {str(e)}")
return False
@staticmethod
@csrf_exempt
@require_POST
@ -364,45 +599,45 @@ class PaymentService:
)
print(f"Tournament webhook event type: {event['type']}")
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
metadata = session.get('metadata', {})
tournament_id = metadata.get('tournament_id')
if not tournament_id:
print("No tournament_id in metadata")
return HttpResponse(status=400)
stripe_object = event['data']['object']
payment_service = PaymentService(request)
success = payment_service.process_successful_payment(tournament_id, session)
# Debug: Print the object type
object_type = stripe_object.get('object', 'unknown')
print(f"Stripe object type: {object_type}")
if event['type'] == 'checkout.session.completed':
success = PaymentService.process_direct_payment(stripe_object)
if success:
print(f"Successfully processed webhook payment for tournament {tournament_id}")
print(f"Successfully processed completed checkout session")
return HttpResponse(status=200)
else:
print(f"Failed to process webhook payment for tournament {tournament_id}")
print(f"Failed to process completed checkout session")
return HttpResponse(status=400)
elif event['type'] == 'payment_intent.payment_failed':
intent = event['data']['object']
metadata = intent.get('metadata', {})
success = PaymentService.process_failed_payment_intent(stripe_object)
if success:
print(f"Successfully processed failed payment intent")
return HttpResponse(status=200)
else:
print(f"Failed to process failed payment intent")
return HttpResponse(status=400)
tournament_id = metadata.get('tournament_id')
source_page = metadata.get('source_page')
elif event['type'] == 'checkout.session.expired':
success = PaymentService.process_expired_checkout_session(stripe_object)
if success:
print(f"Successfully processed expired checkout session")
return HttpResponse(status=200)
else:
print(f"Failed to process expired checkout session")
return HttpResponse(status=400)
if tournament_id and source_page == 'register_tournament':
try:
tournament = Tournament.objects.get(id=tournament_id)
# Decrease reserved spots, minimum 0
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
tournament.save()
print(f"Decreased reserved spots for tournament {tournament_id} after payment failure")
except Tournament.DoesNotExist:
print(f"Tournament {tournament_id} not found")
except Exception as e:
print(f"Error updating tournament reserved spots: {str(e)}")
return HttpResponse(status=200)
else:
print(f"Unhandled event type: {event['type']}")
return HttpResponse(status=200)
except Exception as e:
print(f"Tournament webhook error: {str(e)}")
import traceback
traceback.print_exc()
return HttpResponse(status=400)

@ -357,8 +357,9 @@ class RegistrationCartManager:
return True, "Informations de contact mises à jour."
def checkout(self):
def checkout(self, confirmed):
"""Convert cart to an actual tournament registration"""
print("Checkout")
if self.is_cart_expired():
return False, "Votre session d'inscription a expiré, veuillez réessayer."
@ -399,8 +400,12 @@ class RegistrationCartManager:
weight = sum(int(player_data.get('computed_rank', 0) or 0) for player_data in players)
# Create team registration
if confirmed:
target_tournament = tournament
else:
target_tournament = None
team_registration = TeamRegistration.objects.create(
tournament=tournament,
tournament=target_tournament,
registration_date=timezone.now(),
walk_out=False,
weight=weight,
@ -421,15 +426,16 @@ class RegistrationCartManager:
data_source = PlayerDataSource.FRENCH_FEDERATION # Now using the enum value
User = get_user_model()
matching_user = self.request.user
if player_licence_id and (stripped_license is None or is_captain is False):
try:
# Using icontains for case-insensitive match
matching_user = User.objects.get(licence_id__icontains=player_licence_id)
if matching_user is None:
matching_user = self.request.user
except User.DoesNotExist:
pass
if is_captain:
matching_user = self.request.user
else:
matching_user = None
if player_licence_id and (stripped_license is None or is_captain is False):
try:
# Using icontains for case-insensitive match
matching_user = User.objects.get(licence_id__icontains=player_licence_id)
except User.DoesNotExist:
pass
is_woman = player_data.get('is_woman')
if is_woman is not None:
@ -486,8 +492,10 @@ class RegistrationCartManager:
# Clear the cart
self.clear_cart()
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
tournament.save()
if confirmed:
tournament.reserved_spots = max(0, tournament.reserved_spots - 1)
tournament.save()
return True, team_registration

@ -1385,12 +1385,17 @@ def tournament_payment_success(request, tournament_id):
try:
# Verify payment status with Stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
checkout_session = stripe.checkout.Session.retrieve(checkout_session_id)
stripe_account_id = request.session.get('stripe_account_id')
if not stripe_account_id:
checkout_session = stripe.checkout.Session.retrieve(checkout_session_id)
else:
checkout_session = stripe.checkout.Session.retrieve(checkout_session_id, stripe_account=stripe_account_id)
if checkout_session.payment_status == 'paid':
# Process the payment success
payment_service = PaymentService(request)
success = payment_service.process_successful_payment(str(tournament_id), checkout_session)
success = payment_service.process_successful_payment(checkout_session)
if success:
# Set a flag for successful registration if the payment was from registration page
@ -1413,7 +1418,7 @@ def tournament_payment_success(request, tournament_id):
source_page = request.session.get('payment_source_page', 'tournament_info')
# Clean up session variables
for key in ['stripe_checkout_session_id', 'team_registration_id', 'payment_source_page']:
for key in ['stripe_checkout_session_id', 'team_registration_id', 'payment_source_page', 'stripe_account_id']:
if key in request.session:
del request.session[key]
@ -1429,11 +1434,51 @@ def tournament_payment_success(request, tournament_id):
@csrf_protect
def register_tournament(request, tournament_id):
tournament = get_object_or_404(Tournament, id=tournament_id)
# Print full session content for debugging
print("=" * 60)
print(f"[SESSION DEBUG] register_tournament called for tournament {tournament_id}")
print(f"[SESSION DEBUG] Request method: {request.method}")
print(f"[SESSION DEBUG] Session key: {request.session.session_key}")
print("[SESSION DEBUG] Full session contents:")
for key, value in request.session.items():
print(f" {key}: {value}")
print("=" * 60)
# Check for registration_successful flag
registration_successful = request.session.pop('registration_successful', False)
registration_paid = request.session.pop('registration_paid', False)
# Handle payment cancellation - check for cancelled team registration
cancel_team_registration_id = request.session.pop('cancel_team_registration_id', None)
if cancel_team_registration_id:
print(f"[PAYMENT CANCEL] Handling cancelled team registration: {cancel_team_registration_id}")
try:
# Get the team registration that was created but payment was cancelled
team_registration = TeamRegistration.objects.get(id=cancel_team_registration_id)
# Release the reserved spot if this was a cart registration
if tournament.reserved_spots > 0:
tournament.reserved_spots -= 1
tournament.save()
print(f"[PAYMENT CANCEL] Released reserved spot for tournament {tournament_id}")
# Delete the unpaid team registration if it's not paid
if not team_registration.is_paid():
team_registration.delete()
print(f"[PAYMENT CANCEL] Deleted unpaid team registration {cancel_team_registration_id}")
except TeamRegistration.DoesNotExist:
print(f"[PAYMENT CANCEL] Team registration {cancel_team_registration_id} not found")
except Exception as e:
print(f"[PAYMENT CANCEL] Error handling cancellation: {str(e)}")
# Clean up any other payment-related session data
for key in ['stripe_checkout_session_id', 'team_registration_id', 'cancel_team_registration_id', 'stripe_account_id']:
request.session.pop(key, None)
# Add a user message about the cancellation
messages.warning(request, "Paiement annulé. Vous pouvez relancer votre inscription ci-dessous.")
# If registration was successful, render success page immediately
if registration_successful:
storage = messages.get_messages(request)
@ -1470,27 +1515,21 @@ def register_tournament(request, tournament_id):
# Only initialize a fresh cart for GET requests
# For POST requests, use the existing cart to maintain state
if request.method == 'GET':
# Check if we're returning from Stripe (don't reinitialize if we have a checkout session)
if 'stripe_checkout_session_id' in request.session:
# We're returning from Stripe, don't reinitialize the cart
print("Initializing cart")
storage = messages.get_messages(request)
for _ in storage:
pass
else:
print("Initializing cart")
storage = messages.get_messages(request)
for _ in storage:
pass
if len(storage._loaded_messages) == 1:
del storage._loaded_messages[0]
if len(storage._loaded_messages) == 1:
del storage._loaded_messages[0]
# ALWAYS initialize a fresh cart when entering the registration page (GET request)
# This ensures no old cart data persists
cart_manager.initialize_cart(tournament_id)
# ALWAYS initialize a fresh cart when entering the registration page (GET request)
# This ensures no old cart data persists
cart_manager.initialize_cart(tournament_id)
# Auto-add the authenticated user with license
if request.user.is_authenticated and request.user.licence_id:
cart_manager.add_authenticated_user()
# Auto-add the authenticated user with license
if request.user.is_authenticated and request.user.licence_id:
cart_manager.add_authenticated_user()
else:
# For POST, ensure tournament ID is correct
current_tournament_id = cart_manager.get_tournament_id()
@ -1532,7 +1571,7 @@ def register_tournament(request, tournament_id):
elif 'register_team' in request.POST:
handle_register_team_request(request, tournament, cart_manager, context)
elif 'proceed_to_payment' in request.POST:
result = handle_payment_request(request, tournament, cart_manager, context, tournament_id)
result = handle_payment_request(request, cart_manager, context, tournament_id)
if result:
return result # This is the redirect to Stripe checkout
@ -1659,7 +1698,7 @@ def handle_register_team_request(request, tournament, cart_manager, context):
print_session_debug(request, "SESSION DUMP BEFORE CHECKOUT")
# Checkout and create registration
success, result = cart_manager.checkout()
success, result = cart_manager.checkout(confirmed=True)
if success:
waiting_list_position = cart_data.get('waiting_list_position', -1)
if is_not_sqlite_backend():
@ -1683,7 +1722,7 @@ def handle_register_team_request(request, tournament, cart_manager, context):
messages.error(request, f"{field}: {error}")
context['team_form'] = team_form
def handle_payment_request(request, tournament, cart_manager, context, tournament_id):
def handle_payment_request(request, cart_manager, context, tournament_id):
"""Handle the 'proceed_to_payment' POST action"""
team_form = TournamentRegistrationForm(request.POST)
if team_form.is_valid():
@ -1694,15 +1733,12 @@ def handle_payment_request(request, tournament, cart_manager, context, tournamen
# Create payment session
try:
# Get cart data for payment metadata
cart_data = cart_manager.get_cart_data()
# Create and redirect to payment session
payment_service = PaymentService(request)
checkout_session = payment_service.create_checkout_session(
tournament_id=tournament_id,
team_fee=cart_manager.team_fee_from_cart_players(), # Use the appropriate fee field
cart_data=cart_data
cart_manager=cart_manager
)
# Redirect to Stripe checkout

Loading…
Cancel
Save