You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
17 KiB
401 lines
17 KiB
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 - 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
|
|
})
|
|
|
|
refund = stripe.Refund.create(**refund_params)
|
|
|
|
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, "L'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)}")
|
|
|