diff --git a/api/urls.py b/api/urls.py index 5e9d5ff..f82d616 100644 --- a/api/urls.py +++ b/api/urls.py @@ -63,5 +63,6 @@ urlpatterns = [ path('dj-rest-auth/', include('dj_rest_auth.urls')), path('stripe/create-account/', views.create_stripe_connect_account, name='create_stripe_account'), path('stripe/create-account-link/', views.create_stripe_account_link, name='create_account_link'), + path('resend-payment-email//', views.resend_payment_email, name='resend-payment-email'), ] diff --git a/api/views.py b/api/views.py index e21a8d8..06e16bc 100644 --- a/api/views.py +++ b/api/views.py @@ -16,6 +16,7 @@ from django.shortcuts import get_object_or_404 from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, DrawLogSerializer, TournamentSerializer, UserSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, PurchaseSerializer, ShortUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, CustomUserSerializer, UnregisteredTeamSerializer, UnregisteredPlayerSerializer, ImageSerializer, ActivitySerializer, ProspectSerializer, EntitySerializer, TournamentSummarySerializer from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer, Image +from tournaments.services.email_service import TournamentEmailService from biz.models import Activity, Prospect, Entity @@ -619,6 +620,33 @@ def validate_stripe_account(request): 'needs_onboarding': True, }, status=200) +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def resend_payment_email(request, team_registration_id): + """ + Resend the registration confirmation email (which includes payment info/link) + """ + try: + team_registration = TeamRegistration.objects.get(id=team_registration_id) + tournament = team_registration.tournament + + TournamentEmailService.send_registration_confirmation( + request, + tournament, + team_registration, + waiting_list_position=-1 + ) + + return Response({ + 'success': True, + 'message': 'Email de paiement renvoyé' + }) + + except TeamRegistration.DoesNotExist: + return Response({'error': 'Team not found'}, status=status.HTTP_404_NOT_FOUND) + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + @api_view(['GET']) @permission_classes([IsAuthenticated]) def is_granted_unlimited_access(request): diff --git a/padelclub_backend/settings_local.py.dist b/padelclub_backend/settings_local.py.dist index f983539..5a4dc7c 100644 --- a/padelclub_backend/settings_local.py.dist +++ b/padelclub_backend/settings_local.py.dist @@ -42,6 +42,7 @@ STRIPE_PUBLISHABLE_KEY = '' STRIPE_SECRET_KEY = '' SHOP_STRIPE_WEBHOOK_SECRET = 'whsec_...' # Your existing webhook secret TOURNAMENT_STRIPE_WEBHOOK_SECRET = 'whsec_...' # New webhook secret for tournaments +XLR_STRIPE_WEBHOOK_SECRET = 'whsec_...' # New webhook secret for padel club STRIPE_FEE = 0.0075 TOURNAMENT_SETTINGS = { 'TIME_PROXIMITY_RULES': { diff --git a/tournaments/forms.py b/tournaments/forms.py index 3c7c7f8..e38edeb 100644 --- a/tournaments/forms.py +++ b/tournaments/forms.py @@ -171,7 +171,7 @@ class SimpleForm(forms.Form): class TournamentRegistrationForm(forms.Form): #first_name = forms.CharField(label='Prénom', max_length=50) #last_name = forms.CharField(label='Nom', max_length=50) - email = forms.EmailField(label='E-mail', widget=forms.EmailInput(attrs={'readonly': 'readonly'})) + email = forms.EmailField(label='E-mail') mobile_number = forms.CharField( label='Téléphone', max_length=15, diff --git a/tournaments/models/player_registration.py b/tournaments/models/player_registration.py index 81879bd..13a7018 100644 --- a/tournaments/models/player_registration.py +++ b/tournaments/models/player_registration.py @@ -228,3 +228,8 @@ class PlayerRegistration(TournamentSubModel): return self.team_registration.tournament.entry_fee else: return 0 + + def player_contact(self): + if self.contact_email: + return self.contact_email + return self.email diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py index 6094e9d..09c3e66 100644 --- a/tournaments/models/team_registration.py +++ b/tournaments/models/team_registration.py @@ -550,3 +550,13 @@ class TeamRegistration(TournamentSubModel): # Check payment status for each player payment_statuses = [player.get_player_registration_fee() for player in player_registrations] return sum(payment_statuses) + + def team_contact(self): + if self.user: + return self.user.email + else: + player_registrations = self.players_sorted_by_captain + if len(player_registrations) > 0: + return player_registrations[0].player_contact() + + return None diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index 8b5ed54..9b54041 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -516,8 +516,8 @@ class TournamentEmailService: @staticmethod def notify(captain, other_player, tournament, message_type: TeamEmailType): - print("TournamentEmailService.notify", captain.email, captain.registered_online, tournament, message_type) - if not captain or not captain.registered_online or not captain.email: + print("TournamentEmailService.notify", captain.player_contact(), captain.registered_online, tournament, message_type) + if not captain or not captain.registered_online or not captain.player_contact(): return tournament_details_str = tournament.build_tournament_details_str() @@ -531,7 +531,7 @@ class TournamentEmailService: topic = message_type.email_topic(tournament.federal_level_category, captain.time_to_confirm) email_subject = TournamentEmailService.email_subject(tournament, topic) - TournamentEmailService._send_email(captain.email, email_subject, email_body) + TournamentEmailService._send_email(captain.player_contact(), email_subject, email_body) @staticmethod def _build_email_content(message_type, recipient, tournament, tournament_details_str, other_player, request=None, waiting_list_position=None): @@ -607,7 +607,7 @@ class TournamentEmailService: print("TournamentEmailService.notify_team 2p", team) first_player, second_player = players TournamentEmailService.notify(first_player, second_player, tournament, message_type) - if first_player.email != second_player.email: + if first_player.player_contact() != second_player.player_contact(): TournamentEmailService.notify(second_player, first_player, tournament, message_type) elif len(players) == 1: print("TournamentEmailService.notify_team 1p", team) @@ -681,12 +681,38 @@ class TournamentEmailService: # For unpaid teams, add payment instructions formatted_fee = currency_service.format_amount(tournament.entry_fee, tournament.currency_code) - payment_info = [ - "\n\n⚠️ Paiement des frais d'inscription requis", - f"Les frais d'inscription de {formatted_fee} par joueur doivent être payés pour confirmer votre participation.", - "Vous pouvez effectuer le paiement en vous connectant à votre compte Padel Club.", - f"Lien pour payer: https://padelclub.app/tournament/{tournament.id}/info" - ] + + print("team_registration.user", team_registration.user) + # Check if team has a user account attached + if team_registration.user: + # User has account - direct to login and pay + payment_info = [ + "\n\n⚠️ Paiement des frais d'inscription requis", + f"Les frais d'inscription de {formatted_fee} par joueur doivent être payés pour confirmer votre participation.", + "Vous pouvez effectuer le paiement en vous connectant à votre compte Padel Club.", + f"Lien pour payer: https://padelclub.app/tournament/{tournament.id}/info" + ] + else: + # No user account - create payment link + from .payment_service import PaymentService + payment_link = PaymentService.create_payment_link(team_registration.id) + + if payment_link: + payment_info = [ + "\n\n⚠️ Paiement des frais d'inscription requis", + f"Les frais d'inscription de {formatted_fee} par joueur doivent être payés pour confirmer votre participation.", + "Vous pouvez effectuer le paiement directement via ce lien sécurisé :", + f"💳 Payer maintenant: {payment_link}", + "\nAucun compte n'est requis pour effectuer le paiement." + ] + else: + # Fallback if payment link creation fails + payment_info = [ + "\n\n⚠️ Paiement des frais d'inscription requis", + f"Les frais d'inscription de {formatted_fee} par joueur doivent être payés pour confirmer votre participation.", + "Veuillez contacter l'organisateur du tournoi pour effectuer le paiement.", + f"Informations du tournoi: https://padelclub.app/tournament/{tournament.id}/info" + ] return "\n".join(payment_info) @@ -721,11 +747,13 @@ class TournamentEmailService: tournament_prefix_that = federal_level_category.localized_prefix_that() processed_emails = set() for player in player_registrations: - if not player.email or not player.registered_online: + # Check both email and contact_email fields + player_email = player.player_contact() + if not player_email or not player.registered_online: continue - if player.email in processed_emails: + if player_email in processed_emails: continue - processed_emails.add(player.email) + processed_emails.add(player_email) tournament_details_str = tournament.build_tournament_details_str() other_player = team_registration.get_other_player(player) if len(player_registrations) > 1 else None @@ -772,7 +800,7 @@ class TournamentEmailService: email_body = "".join(body_parts) email_subject = TournamentEmailService.email_subject(tournament, "Confirmation de paiement") - TournamentEmailService._send_email(player.email, email_subject, email_body) + TournamentEmailService._send_email(player.player_contact(), email_subject, email_body) @staticmethod def send_refund_confirmation(tournament, team_registration, refund_details): @@ -812,11 +840,11 @@ class TournamentEmailService: tournament_prefix_that = federal_level_category.localized_prefix_that() processed_emails = set() for player in player_registrations: - if not player.email or not player.registered_online: + if not player.player_contact() or not player.registered_online: continue - if player.email in processed_emails: + if player.player_contact() in processed_emails: continue - processed_emails.add(player.email) + processed_emails.add(player.player_contact()) tournament_details_str = tournament.build_tournament_details_str() other_player = team_registration.get_other_player(player) if len(player_registrations) > 1 else None @@ -856,4 +884,4 @@ class TournamentEmailService: email_body = "".join(body_parts) email_subject = TournamentEmailService.email_subject(tournament, "Confirmation de remboursement") - TournamentEmailService._send_email(player.email, email_subject, email_body) + TournamentEmailService._send_email(player.player_contact(), email_subject, email_body) diff --git a/tournaments/services/payment_service.py b/tournaments/services/payment_service.py index 0dd8430..8c99a5e 100644 --- a/tournaments/services/payment_service.py +++ b/tournaments/services/payment_service.py @@ -76,6 +76,8 @@ class PaymentService: 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") + if not customer_email: + customer_email = team_registration.team_contact() team_registration_id = team_registration.id print(f"[TOURNAMENT PAYMENT] Created team registration: {team_registration_id}") @@ -590,20 +592,32 @@ class PaymentService: def stripe_webhook(request): payload = request.body sig_header = request.META.get('HTTP_STRIPE_SIGNATURE') - print("Received webhook call") + + # Check if this is a Connect account webhook + stripe_account = request.META.get('HTTP_STRIPE_ACCOUNT') + + print("=== WEBHOOK DEBUG ===") print(f"Signature: {sig_header}") + print(f"Connect Account: {stripe_account}") + + if stripe_account: + # This is a connected account (regular umpire tournament) + webhook_secret = settings.TOURNAMENT_STRIPE_WEBHOOK_SECRET + print(f"Using umpire webhook secret for connected account: {stripe_account}") + else: + # This is platform account (corporate tournament) + webhook_secret = settings.XLR_STRIPE_WEBHOOK_SECRET + print("Using XLR company webhook secret") try: - event = stripe.Webhook.construct_event( - payload, sig_header, settings.TOURNAMENT_STRIPE_WEBHOOK_SECRET - ) + event = stripe.Webhook.construct_event(payload, sig_header, webhook_secret) print(f"Tournament webhook event type: {event['type']}") + # Debug metadata stripe_object = event['data']['object'] - - # Debug: Print the object type - object_type = stripe_object.get('object', 'unknown') - print(f"Stripe object type: {object_type}") + metadata = stripe_object.get('metadata', {}) + print(f"is_corporate_tournament: {metadata.get('is_corporate_tournament', 'unknown')}") + print(f"payment_source: {metadata.get('payment_source', 'unknown')}") if event['type'] == 'checkout.session.completed': success = PaymentService.process_direct_payment(stripe_object) @@ -637,7 +651,123 @@ class PaymentService: return HttpResponse(status=200) except Exception as e: - print(f"Tournament webhook error: {str(e)}") + print(f"Webhook error: {str(e)}") import traceback traceback.print_exc() return HttpResponse(status=400) + + @staticmethod + def create_payment_link(team_registration_id): + """ + Create a Stripe Payment Link for a team registration + Returns the payment link URL or None if failed + """ + try: + team_registration = TeamRegistration.objects.get(id=team_registration_id) + tournament = team_registration.tournament + + if not tournament or tournament.is_free(): + return None + + stripe.api_key = settings.STRIPE_SECRET_KEY + currency_service = CurrencyService() + + # Calculate the team fee + team_fee = team_registration.get_team_registration_fee() + stripe_amount = currency_service.convert_to_stripe_amount(team_fee, tournament.currency_code) + + customer_email = team_registration.team_contact() + currency_code = tournament.currency_code or 'EUR' + + print(f"[PAYMENT LINK] Tournament: {tournament.name}") + print(f"[PAYMENT LINK] is_corporate_tournament: {tournament.is_corporate_tournament}") + + # Base metadata (same as checkout session) + base_metadata = { + 'tournament_id': str(tournament.id), + 'team_registration_id': str(team_registration.id), + 'customer_email': customer_email or 'Non fourni', + 'payment_source': 'payment_link', + 'registration_type': 'direct', + 'currency_code': currency_code, + } + + # Create payment link params + payment_link_params = { + 'line_items': [{ + 'price_data': { + '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, + }, + 'quantity': 1, + }], + 'after_completion': { + 'type': 'redirect', + 'redirect': { + 'url': f'https://padelclub.app/stripe/payment_complete/?tournament_id={tournament.id}&team_registration_id={team_registration.id}&payment=success' + } + }, + 'automatic_tax': {'enabled': False}, + 'billing_address_collection': 'auto', + } + + # Handle corporate vs regular tournaments (same logic as checkout session) + if tournament.is_corporate_tournament: + print(f"[PAYMENT LINK] Corporate tournament - creating on platform account") + # Corporate tournament - create on platform account (no Connect account) + metadata = { + **base_metadata, + 'is_corporate_tournament': 'true', + 'stripe_account_type': 'direct' + } + payment_link_params['metadata'] = metadata + + # Create payment link on platform account + payment_link = stripe.PaymentLink.create(**payment_link_params) + + else: + print(f"[PAYMENT LINK] Regular tournament - creating on connected account") + # Regular tournament - create on connected account + stripe_account_id = tournament.stripe_account_id + if not stripe_account_id: + print(f"[PAYMENT LINK] ERROR: No Stripe account ID for umpire") + return None + + metadata = { + **base_metadata, + 'is_corporate_tournament': 'false', + 'stripe_account_type': 'connect', + 'stripe_account_id': stripe_account_id + } + payment_link_params['metadata'] = metadata + + print(f"[PAYMENT LINK] Creating payment link for connected account: {stripe_account_id}") + + # Create payment link on connected account + payment_link = stripe.PaymentLink.create( + **payment_link_params, + stripe_account=stripe_account_id + ) + + print(f"[PAYMENT LINK] Created payment link: {payment_link.url}") + return payment_link.url + + except Exception as e: + print(f"[PAYMENT LINK] Error creating payment link: {str(e)}") + import traceback + traceback.print_exc() + return None + + @staticmethod + def get_or_create_payment_link(team_registration_id): + """ + Get existing payment link or create a new one for a team registration + This method can be used to avoid creating multiple links for the same registration + """ + # In a real implementation, you might want to store payment links in the database + # and check if one already exists and is still valid + return PaymentService.create_payment_link(team_registration_id) diff --git a/tournaments/services/tournament_registration.py b/tournaments/services/tournament_registration.py index 1e8630b..3228177 100644 --- a/tournaments/services/tournament_registration.py +++ b/tournaments/services/tournament_registration.py @@ -38,7 +38,8 @@ class RegistrationCartManager: 'registration_tournament_id', 'registration_cart_players', 'registration_cart_expiry', - 'registration_mobile_number' + 'registration_mobile_number', + 'registration_email' ] for key in keys_to_delete: if key in self.session: @@ -132,9 +133,12 @@ class RegistrationCartManager: # Get user phone if authenticated user_phone = '' + user_email = '' if hasattr(self.request.user, 'phone'): user_phone = self.request.user.phone + if hasattr(self.request.user, 'email'): + user_email = self.request.user.email # Parse the expiry time from ISO format to datetime expiry_str = self.get_cart_expiry() expiry_datetime = None @@ -155,7 +159,8 @@ class RegistrationCartManager: 'is_cart_expired': self.is_cart_expired(), 'team_fee_from_cart_players': self.team_fee_from_cart_players(), 'team_fee_from_cart_players_formatted': self.team_fee_from_cart_players_formatted(), - 'mobile_number': self.session.get('registration_mobile_number', user_phone) + 'mobile_number': self.session.get('registration_mobile_number', user_phone), + 'email': self.session.get('registration_email', user_email), } # Debug: print the cart content @@ -370,11 +375,13 @@ class RegistrationCartManager: return True, "Joueur retiré." - def update_contact_info(self, mobile_number=None): + def update_contact_info(self, email=None, mobile_number=None): """Update contact info for the cart""" if self.is_cart_expired(): return False, "Votre session d'inscription a expiré, veuillez réessayer." + if email is not None: + self.session['registration_email'] = email if mobile_number is not None: self.session['registration_mobile_number'] = mobile_number @@ -394,6 +401,7 @@ class RegistrationCartManager: tournament_id = cart_data.get('tournament_id') players = cart_data.get('players') mobile_number = cart_data.get('mobile_number') + email = cart_data.get('email') # Validate cart data if not tournament_id: @@ -435,7 +443,7 @@ class RegistrationCartManager: registration_date=timezone.now(), walk_out=False, weight=weight, - user=self.request.user + user= self.request.user if self.request.user.is_authenticated else None ) for player_data in players: # Compute rank and sex using the original logic @@ -511,8 +519,8 @@ class RegistrationCartManager: rank=player_data.get('rank'), computed_rank=player_data.get('computed_rank'), licence_id=player_data.get('licence_id'), - email=matching_user.email if matching_user else player_data.get('email'), - phone_number=matching_user.phone if matching_user else player_data.get('mobile_number'), + contact_email=matching_user.email if matching_user else player_data.get('email', email), + contact_phone_number=matching_user.phone if matching_user else player_data.get('mobile_number', mobile_number), registration_status=RegistrationStatus.CONFIRMED if self.session.get('waiting_list_position', 0) < 0 else RegistrationStatus.WAITING ) @@ -533,7 +541,8 @@ class RegistrationCartManager: 'registration_tournament_id', 'registration_cart_players', 'registration_cart_expiry', - 'registration_mobile_number' + 'registration_mobile_number', + 'registration_email', ] for key in keys_to_clear: diff --git a/tournaments/templates/register_tournament.html b/tournaments/templates/register_tournament.html index d5746b3..b25cb0e 100644 --- a/tournaments/templates/register_tournament.html +++ b/tournaments/templates/register_tournament.html @@ -32,7 +32,13 @@

✅ Votre paiement a bien été effectué et enregistré.

{% endif %}

- Un email de confirmation a été envoyé à l'adresse associée à votre compte Padel Club ({{ user.email }}). Pensez à vérifier vos spams si vous ne recevez pas l'email. En cas de problème, contactez le juge-arbitre. + {% if user.email %} + Un email de confirmation a été envoyé à l'adresse associée à votre compte Padel Club ({{ user.email }}). Pensez à vérifier vos spams si vous ne recevez pas l'email. En cas de problème, contactez le juge-arbitre. + {% elif registered_team.team_contact %} + Un email de confirmation a été envoyé à l'adresse associée à votre compte Padel Club ({{ registered_team.team_contact }}). Pensez à vérifier vos spams si vous ne recevez pas l'email. En cas de problème, contactez le juge-arbitre. + {% else %} + Aucun email de confirmation n'a été envoyé car vous n'avez pas fourni d'adresse email. Contactez le juge-arbitre. + {% endif %}

{% else %} {% if not registration_successful %} diff --git a/tournaments/templates/stripe/payment_complete.html b/tournaments/templates/stripe/payment_complete.html new file mode 100644 index 0000000..c0f1db7 --- /dev/null +++ b/tournaments/templates/stripe/payment_complete.html @@ -0,0 +1,30 @@ +{% extends 'tournaments/base.html' %} +{% block head_title %} Paiement {% endblock %} +{% block first_title %} Padel Club {% endblock %} +{% block second_title %} Paiement {% endblock %} + +{% block content %} +{% load static %} +{% load tz %} + +
+
+
+ {% if payment_status == 'success' %} + +

Votre inscription a été confirmée. Un email de confirmation vous a été envoyé.

+ {% if show_details and tournament %} +

Tournoi : {{ tournament.display_name }}

+ {% endif %} + {% elif payment_status == 'cancel' %} + +

Votre paiement a été annulé. Aucun montant n'a été prélevé.

+ {% else %} + +

Votre paiement est en cours de traitement. Vous recevrez un email de confirmation sous peu.

+ {% endif %} +

Vous pouvez maintenant fermer cette page.

+
+
+
+{% endblock %} diff --git a/tournaments/urls.py b/tournaments/urls.py index 255845a..b9fd316 100644 --- a/tournaments/urls.py +++ b/tournaments/urls.py @@ -79,6 +79,7 @@ urlpatterns = [ path('activation-success/', views.activation_success, name='activation_success'), path('activation-failed/', views.activation_failed, name='activation_failed'), path('tournaments//confirm/', views.confirm_tournament_registration, name='confirm_tournament_registration'), + path('stripe/payment_complete/', views.stripe_payment_complete, name='stripe-payment-complete'), path('stripe-onboarding-complete/', views.stripe_onboarding_complete, name='stripe-onboarding-complete'), path('stripe-refresh-account-link/', views.stripe_refresh_account_link, name='stripe-refresh-account-link'), path('tournaments//toggle-private/', views.toggle_tournament_private, name='toggle_tournament_private'), diff --git a/tournaments/views.py b/tournaments/views.py index 41226ba..3adce17 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -1583,9 +1583,14 @@ def proceed_to_payment(request, tournament_id): messages.error(request, f"Erreur lors de la création de la session de paiement: {str(e)}") return redirect('tournament-info', tournament_id=tournament_id) -@login_required def tournament_payment_success(request, tournament_id): """Handle successful Stripe payment for tournament registration""" + + # For unauthenticated users, process payment and redirect directly to registration page + if not request.user.is_authenticated: + return _handle_unauthenticated_payment_success(request, tournament_id) + + # Original logic for authenticated users # Get checkout session ID from session checkout_session_id = request.session.get('stripe_checkout_session_id') if not checkout_session_id: @@ -1641,6 +1646,68 @@ def tournament_payment_success(request, tournament_id): # For direct payments, go to tournament info return redirect('tournament-info', tournament_id=tournament_id) + +def _handle_unauthenticated_payment_success(request, tournament_id): + """Handle payment success for unauthenticated users""" + print(f"[PAYMENT SUCCESS] Handling unauthenticated user payment for tournament {tournament_id}") + + # Get checkout session ID from session + checkout_session_id = request.session.get('stripe_checkout_session_id') + if not checkout_session_id: + print(f"[PAYMENT SUCCESS] No checkout session ID found") + messages.error(request, "Session de paiement introuvable.") + return redirect('register_tournament', tournament_id=tournament_id) + + try: + # Verify payment status with Stripe + stripe.api_key = settings.STRIPE_SECRET_KEY + print(f"[PAYMENT SUCCESS] Retrieving checkout session: {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) + + print(f"[PAYMENT SUCCESS] Payment status: {checkout_session.payment_status}") + + if checkout_session.payment_status == 'paid': + # Process the payment success + payment_service = PaymentService(request) + success = payment_service.process_successful_payment(checkout_session) + + print(f"[PAYMENT SUCCESS] Payment processing success: {success}") + + if success: + # Always set success flags for unauthenticated users since they come from registration + request.session['registration_successful'] = True + request.session['registration_paid'] = True + + # Clear payment-related session data + 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] + + print(f"[PAYMENT SUCCESS] Redirecting to registration page with success flags") + # Redirect directly to registration page with success context + return redirect('register_tournament', tournament_id=tournament_id) + else: + messages.error(request, "Erreur lors du traitement du paiement.") + else: + messages.error(request, "Le paiement n'a pas été complété.") + + except Exception as e: + print(f"[PAYMENT SUCCESS] Payment processing error: {str(e)}") + messages.error(request, f"Erreur lors de la vérification du paiement: {str(e)}") + + # Clean up session variables even if there was an error + 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] + + # Always redirect to registration page for unauthenticated users + return redirect('register_tournament', tournament_id=tournament_id) + @csrf_protect def register_tournament(request, tournament_id): tournament = get_object_or_404(Tournament, id=tournament_id) @@ -1657,7 +1724,7 @@ def register_tournament(request, tournament_id): # Check for registration_successful flag registration_successful = request.session.pop('registration_successful', False) registration_paid = request.session.pop('registration_paid', False) - + registered_team = None # 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: @@ -1676,7 +1743,8 @@ def register_tournament(request, tournament_id): if not team_registration.is_paid(): team_registration.delete() print(f"[PAYMENT CANCEL] Deleted unpaid team registration {cancel_team_registration_id}") - + else: + registered_team = team_registration except TeamRegistration.DoesNotExist: print(f"[PAYMENT CANCEL] Team registration {cancel_team_registration_id} not found") except Exception as e: @@ -1702,6 +1770,7 @@ def register_tournament(request, tournament_id): 'tournament': tournament, 'registration_successful': True, 'registration_paid': registration_paid, + 'registered_team': registered_team, 'current_players': [], 'cart_data': {'players': []} } @@ -1823,6 +1892,7 @@ def handle_add_player_request(request, tournament, cart_manager, context): if team_form.is_valid(): # Update cart with mobile number before adding player cart_manager.update_contact_info( + email=team_form.cleaned_data.get('email'), mobile_number=team_form.cleaned_data.get('mobile_number') ) @@ -1834,7 +1904,7 @@ def handle_add_player_request(request, tournament, cart_manager, context): context['current_players'] = cart_data['players'] context['cart_data'] = cart_data context['team_form'] = TournamentRegistrationForm(initial={ - 'email': request.user.email if request.user.is_authenticated else '', + 'email': request.user.email if request.user.is_authenticated else cart_data.get('email', ''), 'mobile_number': cart_data.get('mobile_number', '') }) @@ -1898,6 +1968,7 @@ def handle_register_team_request(request, tournament, cart_manager, context): # Update cart with contact info cart_manager.update_contact_info( + email=team_form.cleaned_data.get('email'), mobile_number=team_form.cleaned_data.get('mobile_number') ) @@ -1922,6 +1993,7 @@ def handle_register_team_request(request, tournament, cart_manager, context): ) context['registration_successful'] = True + context['registered_team'] = result context['registration_paid'] = False context['current_players'] = [] context['add_player_form'] = None # No more adding players after success @@ -1939,6 +2011,7 @@ def handle_payment_request(request, cart_manager, context, tournament_id): if team_form.is_valid(): # Update cart with contact info cart_manager.update_contact_info( + email=team_form.cleaned_data.get('email'), mobile_number=team_form.cleaned_data.get('mobile_number') ) @@ -2231,6 +2304,39 @@ def tournament_live_matches(request, tournament_id): 'live_matches': live_matches, }) +def stripe_payment_complete(request): + """Handle payment complete page for Stripe Payment Links""" + tournament_id = request.GET.get('tournament_id') + team_registration_id = request.GET.get('team_registration_id') + payment_status = request.GET.get('payment', 'unknown') + + context = { + 'payment_status': payment_status, + 'tournament': None, + 'team_registration': None, + 'players': [], + 'show_details': False + } + + # Try to get tournament and team registration details + if tournament_id and team_registration_id: + try: + tournament = Tournament.objects.get(id=tournament_id) + team_registration = TeamRegistration.objects.get(id=team_registration_id) + + context.update({ + 'tournament': tournament, + 'team_registration': team_registration, + 'players': team_registration.players_sorted_by_captain, + 'show_details': True, + 'amount_paid': team_registration.get_team_registration_fee(), + 'currency': tournament.currency_code + }) + + except (Tournament.DoesNotExist, TeamRegistration.DoesNotExist): + print(f"Tournament or team registration not found: {tournament_id}, {team_registration_id}") + + return render(request, 'stripe/payment_complete.html', context) class UserListExportView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs):