|
|
|
|
@ -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) |
|
|
|
|
|