from django.shortcuts import get_object_or_404 from django.conf import settings from django.urls import reverse from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST import stripe 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 class PaymentService: """ Service for handling payment processing for tournament registrations """ def __init__(self, request): 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): """ Create a Stripe checkout session for tournament payment """ stripe.api_key = self.stripe_api_key tournament = get_object_or_404(Tournament, id=tournament_id) # Check if payments are enabled for this tournament if not tournament.should_request_payment(): raise Exception("Les paiements ne sont pas activés pour ce tournoi.") # 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 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 cancel_url = self.request.build_absolute_uri( reverse('register_tournament', kwargs={'tournament_id': tournament_id}) ) base_metadata = { 'tournament_id': str(tournament_id), 'user_id': str(self.request.user.id) if self.request.user.is_authenticated else None, 'payment_source': 'tournament', # Identify payment source 'source_page': 'tournament_info' if team_registration_id else 'register_tournament', } if tournament.is_corporate_tournament: # Corporate tournament metadata metadata = { **base_metadata, 'is_corporate_tournament': 'true', 'stripe_account_type': 'direct' } else: # Regular tournament metadata metadata = { **base_metadata, 'is_corporate_tournament': 'false', 'stripe_account_type': 'connect', '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) metadata.update({ 'tournament_name': tournament.broadcast_display_name(), 'tournament_date': tournament.formatted_start_date(), 'tournament_club': tournament.event.club.name, 'tournament_fee': str(team_fee) }) # 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'], 'line_items': [{ 'price_data': { 'currency': 'eur', 'product_data': { 'name': f'{tournament.broadcast_display_name()} du {tournament.formatted_start_date()}', 'description': f'Lieu {tournament.event.club.name}', }, 'unit_amount': int(team_fee * 100), # Amount in cents }, 'quantity': 1, }], 'mode': 'payment', 'success_url': self.request.build_absolute_uri( reverse('tournament-payment-success', kwargs={'tournament_id': tournament_id}) ), 'cancel_url': cancel_url, 'metadata': metadata } else: # Get the umpire's Stripe account ID stripe_account_id = tournament.stripe_account_id if not stripe_account_id: raise Exception("L'arbitre n'a pas configuré son compte Stripe.") # Calculate commission commission_rate = tournament.event.creator.effective_commission_rate() platform_amount = int((team_fee * commission_rate) * 100) # Commission in cents checkout_session_params = { 'payment_method_types': ['card'], 'line_items': [{ 'price_data': { 'currency': 'eur', 'product_data': { 'name': f'{tournament.broadcast_display_name()} du {tournament.formatted_start_date()}', 'description': f'Lieu {tournament.event.club.name}', }, 'unit_amount': int(team_fee * 100), # Amount in cents }, 'quantity': 1, }], 'mode': 'payment', 'success_url': self.request.build_absolute_uri( reverse('tournament-payment-success', kwargs={'tournament_id': tournament_id}) ), 'cancel_url': cancel_url, 'payment_intent_data': { 'application_fee_amount': platform_amount, 'transfer_data': { 'destination': stripe_account_id, }, }, 'metadata': metadata } # # Add cart or team data to metadata based on payment context # if cart_data: # checkout_session_params['metadata']['registration_cart_id'] = str(cart_data['cart_id']) # Convert to string # elif team_registration_id: # checkout_session_params['metadata']['team_registration_id'] = str(team_registration_id) # Convert to string # self.request.session['team_registration_id'] = str(team_registration_id) # Convert to string # Add customer_email if available if customer_email: checkout_session_params['customer_email'] = customer_email # Create the checkout session try: checkout_session = stripe.checkout.Session.create(**checkout_session_params) # 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 return checkout_session except stripe.error.StripeError as 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): """ 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}") # Print combined payment status print(f"Payment statuses: {payment_statuses}") print(any(payment_statuses)) # 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'] 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): """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() if not success: return False # 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 def process_refund(self, team_registration_id): """ Process a refund for a tournament registration as part of unregistration Returns a tuple (success, message) """ stripe.api_key = self.stripe_api_key try: # Get the team registration team_registration = get_object_or_404(TeamRegistration, id=team_registration_id) tournament = team_registration.tournament # Check if refund is possible for this tournament if not tournament.is_refund_possible(): return False, "Les remboursements ne sont plus possibles pour ce tournoi.", None # Get payment ID from player registrations player_registrations = PlayerRegistration.objects.filter(team_registration=team_registration) payment_id = None for player_reg in player_registrations: # Find the first valid payment ID if player_reg.payment_id and player_reg.payment_type == PlayerPaymentType.CREDIT_CARD: payment_id = player_reg.payment_id break 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) 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 refund = stripe.Refund.create( payment_intent=payment_id, refund_application_fee=True, reverse_transfer=True ) for player_reg in player_registrations: player_reg.payment_type = None player_reg.payment_id = None player_reg.save() TournamentEmailService.send_refund_confirmation(tournament, team_registration, refund) # Return success with refund object return True, "Votre inscription a été remboursée automatiquement.", refund except stripe.error.StripeError as e: return False, f"Erreur de remboursement Stripe: {str(e)}", None except Exception as e: return False, f"Erreur lors du remboursement: {str(e)}", None @staticmethod @csrf_exempt @require_POST def stripe_webhook(request): payload = request.body sig_header = request.META.get('HTTP_STRIPE_SIGNATURE') print("Received webhook call") print(f"Signature: {sig_header}") try: event = stripe.Webhook.construct_event( payload, sig_header, settings.TOURNAMENT_STRIPE_WEBHOOK_SECRET ) 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) payment_service = PaymentService(request) success = payment_service.process_successful_payment(tournament_id, session) if success: print(f"Successfully processed webhook payment for tournament {tournament_id}") return HttpResponse(status=200) else: print(f"Failed to process webhook payment for tournament {tournament_id}") return HttpResponse(status=400) elif event['type'] == 'payment_intent.payment_failed': intent = event['data']['object'] metadata = intent.get('metadata', {}) tournament_id = metadata.get('tournament_id') source_page = metadata.get('source_page') 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) except Exception as e: print(f"Tournament webhook error: {str(e)}")