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.
 
 
 
 
padelclub_backend/shop/views.py

580 lines
20 KiB

from .stripe_utils import stripe_service
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from .models import Product, Order, OrderItem, GuestUser, OrderStatus
from django.db.models import Sum
from .forms import GuestCheckoutForm
import stripe
from django.conf import settings
from django.urls import reverse
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from . import cart
# Shared helper methods
def _check_stripe_config():
"""Check if Stripe API keys are properly configured"""
return hasattr(settings, 'STRIPE_SECRET_KEY') and settings.STRIPE_SECRET_KEY
def _create_stripe_line_items(order_items):
"""Create line items for Stripe checkout from order items"""
line_items = []
for item in order_items:
item_price = int(float(item.price) * 100) # Convert to cents
line_items.append({
'price_data': {
'currency': 'eur',
'product_data': {
'name': item.product.title,
'description': f"Color: {item.color.name if item.color else 'N/A'}, Size: {item.size.name if item.size else 'N/A'}",
},
'unit_amount': item_price,
},
'quantity': item.quantity,
})
return line_items
def _create_stripe_checkout_session(request, order, line_items):
"""Create a Stripe checkout session for the order"""
# Create success and cancel URLs
success_url = request.build_absolute_uri(reverse('shop:payment_success', args=[order.id]))
cancel_url = request.build_absolute_uri(reverse('shop:payment_cancel', args=[order.id]))
# Create metadata to identify this order
metadata = {
'order_id': str(order.id),
}
# Add user info to metadata if available
if request.user.is_authenticated:
metadata['user_id'] = str(request.user.id)
elif 'guest_email' in request.session:
metadata['guest_email'] = request.session.get('guest_email', '')
try:
# Use the service to create the session
checkout_session = stripe_service.create_checkout_session(
line_items=line_items,
success_url=success_url,
cancel_url=cancel_url,
metadata=metadata,
)
# Save the checkout session ID to the order
order.stripe_checkout_session_id = checkout_session.id
order.save()
return checkout_session
except Exception as e:
print(f"Stripe error: {str(e)}")
raise
# View functions
def product_list(request):
products = Product.objects.all()
cart_items = cart.get_cart_items(request)
total = cart.get_cart_total(request)
return render(request, 'shop/product_list.html', {
'products': products,
'cart_items': cart_items,
'total': total
})
def view_cart(request):
"""Display the shopping cart"""
cart_items = cart.get_cart_items(request)
total = cart.get_cart_total(request)
total_quantity = cart_items.aggregate(total_quantity=Sum('quantity'))['total_quantity']
display_data = prepare_item_display_data(cart_items, is_cart=True)
context = {
'display_data': display_data,
'total': total,
'total_quantity': total_quantity,
'settings': settings, # Add this line to pass settings to template
}
# Add Stripe publishable key for authenticated users
if request.user.is_authenticated:
context['stripe_publishable_key'] = settings.STRIPE_PUBLISHABLE_KEY
return render(request, 'shop/cart.html', context)
@ensure_csrf_cookie
def add_to_cart_view(request, product_id):
"""Add a product to the cart"""
product = get_object_or_404(Product, id=product_id)
quantity = int(request.POST.get('quantity', 1))
color_id = request.POST.get('color')
size_id = request.POST.get('size')
cart_item = cart.add_to_cart(request, product_id, quantity, color_id, size_id)
messages.success(request, f'{cart_item.quantity} x {product.title} added to your cart')
return redirect('shop:product_list')
def update_cart_view(request, product_id):
"""Update cart item quantity"""
if request.method == 'POST':
quantity = int(request.POST.get('quantity', 0))
cart.update_cart_item(request, product_id, quantity)
return redirect('shop:view_cart')
def remove_from_cart_view(request, product_id):
"""Remove item from cart"""
cart.remove_from_cart(request, product_id)
return redirect('shop:view_cart')
def clear_cart(request):
"""Clear the cart"""
cart.clear_cart(request)
messages.success(request, "Your cart has been cleared.")
return redirect('shop:product_list')
def create_order(request):
"""Create an order from the current cart"""
cart_items = cart.get_cart_items(request)
# Check if cart is empty
if not cart_items.exists():
return None
total_price = sum(item.get_total_price() for item in cart_items)
# Check if total price is valid
if total_price <= 0:
return None
if request.user.is_authenticated:
# Authenticated user order
order = Order.objects.create(
user=request.user,
total_price=total_price,
stripe_mode=stripe_service.mode # Add this line
)
else:
# Guest user order
try:
guest_user = GuestUser.objects.get(email=request.session['guest_email'])
order = Order.objects.create(
guest_user=guest_user,
total_price=total_price,
stripe_mode=stripe_service.mode # Add this line
)
except (KeyError, GuestUser.DoesNotExist):
# No guest user information, create order without user
order = Order.objects.create(
total_price=total_price,
stripe_mode=stripe_service.mode # Add this line
)
# Create order items
for cart_item in cart_items:
OrderItem.objects.create(
order=order,
product=cart_item.product,
quantity=cart_item.quantity,
color=cart_item.color,
size=cart_item.size,
price=cart_item.product.price
)
# Note: Cart is not cleared here, only after successful payment
return order
def checkout(request):
"""Handle checkout process for both authenticated and guest users"""
# Check if cart is empty
cart_items = cart.get_cart_items(request)
if not cart_items.exists():
messages.error(request, "Your cart is empty. Please add items before checkout.")
return redirect('shop:product_list')
if request.user.is_authenticated:
# Create order for authenticated user and go directly to payment
return _handle_authenticated_checkout(request)
# Handle guest checkout
if request.method == 'GET':
form = GuestCheckoutForm()
return render(request, 'shop/checkout.html', {'form': form})
elif request.method == 'POST':
return _handle_guest_checkout_post(request)
return redirect('shop:product_list')
def _handle_authenticated_checkout(request):
"""Helper function to handle checkout for authenticated users"""
order = create_order(request)
if not order:
messages.error(request, "There was an issue creating your order. Please try again.")
return redirect('shop:view_cart')
return redirect('shop:payment', order_id=order.id)
def _handle_guest_checkout_post(request):
"""Helper function to handle POST requests for guest checkout"""
form = GuestCheckoutForm(request.POST)
if form.is_valid():
# Create or get guest user
email = form.cleaned_data['email']
phone = form.cleaned_data['phone']
guest_user, created = GuestUser.objects.get_or_create(
email=email,
defaults={'phone': phone}
)
# Store email in session
request.session['guest_email'] = email
# Create order
order = create_order(request)
if not order:
messages.error(request, "There was an issue creating your order. Please try again.")
return redirect('shop:view_cart')
return redirect('shop:payment', order_id=order.id)
# Form invalid
return render(request, 'shop/checkout.html', {'form': form})
def payment(request, order_id):
"""Handle payment for an existing order"""
order = get_object_or_404(Order, id=order_id)
order.status = OrderStatus.PENDING
order.payment_status = 'PENDING'
order_items = order.items.all()
# Check for valid order
if not order_items.exists() or order.total_price <= 0:
messages.error(request, "Cannot process an empty order.")
return redirect('shop:product_list')
# Log payment processing
print(f"Processing payment for order #{order_id}, total: {order.total_price}")
# Check Stripe configuration
if not _check_stripe_config():
messages.error(request, "Stripe API keys not configured properly.")
return redirect('shop:view_cart')
# Create line items
line_items = _create_stripe_line_items(order_items)
if not line_items:
messages.error(request, "Cannot create payment with no items.")
return redirect('shop:view_cart')
# Create checkout session
try:
checkout_session = _create_stripe_checkout_session(request, order, line_items)
checkout_session_id = checkout_session.id
except Exception as e:
messages.error(request, f"Payment processing error: {str(e)}")
return redirect('shop:view_cart')
display_data = prepare_item_display_data(order_items, is_cart=False)
# Render payment page
return render(request, 'shop/payment.html', {
'order': order,
'display_data': display_data,
'checkout_session_id': checkout_session_id,
'stripe_publishable_key': settings.STRIPE_PUBLISHABLE_KEY,
})
def payment_success(request, order_id):
"""Handle successful payment"""
order = get_object_or_404(Order, id=order_id)
# Clear cart after successful payment
cart.clear_cart(request)
# Only update if not already processed by webhook
if not order.webhook_processed:
print(f"Updating order {order_id} via redirect (not webhook)")
order.status = OrderStatus.PAID
order.payment_status = 'PAID'
order.save()
else:
print(f"Order {order_id} already processed by webhook")
# Get order items for template
order_items = order.items.all()
display_data = prepare_item_display_data(order_items, is_cart=False)
return render(request, 'shop/payment_success.html', {
'order': order,
'display_data': display_data,
})
def payment_cancel(request, order_id):
"""Handle cancelled payment"""
order = get_object_or_404(Order, id=order_id)
# Only update if not already processed by webhook
if not order.webhook_processed:
print(f"Updating order {order_id} to CANCELED via redirect (not webhook)")
order.status = OrderStatus.CANCELED
order.payment_status = 'FAILED'
order.save()
else:
print(f"Order {order_id} already processed by webhook")
messages.warning(request, "Your payment was cancelled.")
return render(request, 'shop/payment_cancel.html', {'order': order})
@require_POST
def create_checkout_session(request):
"""API endpoint to create a Stripe checkout session directly from cart"""
if not request.user.is_authenticated:
return JsonResponse({'error': 'User must be authenticated'}, status=403)
# Create the order
order = create_order(request)
if not order:
return JsonResponse({'error': 'Could not create order from cart'}, status=400)
# Get order items
order_items = order.items.all()
# Create line items
line_items = _create_stripe_line_items(order_items)
# Create checkout session
try:
checkout_session = _create_stripe_checkout_session(request, order, line_items)
return JsonResponse({'id': checkout_session.id})
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)
@require_POST
def update_cart_item(request):
"""Update a cart item quantity (increase/decrease)"""
item_id = request.POST.get('item_id')
action = request.POST.get('action')
try:
cart_item = cart.get_cart_item(request, item_id)
if action == 'increase':
# Increase quantity by 1
cart_item.quantity += 1
cart_item.save()
messages.success(request, "Quantity increased.")
elif action == 'decrease' and cart_item.quantity > 1:
# Decrease quantity by 1
cart_item.quantity -= 1
cart_item.save()
messages.success(request, "Quantity decreased.")
except Exception as e:
messages.error(request, f"Error updating cart: {str(e)}")
return redirect('shop:view_cart')
@require_POST
def remove_from_cart(request):
"""Remove an item from cart by item_id"""
item_id = request.POST.get('item_id')
try:
cart_item = cart.get_cart_item(request, item_id)
cart_item.delete()
messages.success(request, "Item removed from cart.")
except Exception as e:
messages.error(request, f"Error removing item: {str(e)}")
return redirect('shop:view_cart')
def prepare_item_display_data(items, is_cart=True):
"""
Transform cart items or order items into a standardized format for display
Args:
items: QuerySet of CartItem or OrderItem
is_cart: True if items are CartItems, False if OrderItems
Returns:
Dictionary with standardized item data
"""
prepared_items = []
total_quantity = 0
total_price = 0
for item in items:
if is_cart:
# For CartItem
item_data = {
'id': item.id,
'product_title': item.product.title,
'color_name': item.color.name if item.color else 'N/A',
'color_hex': item.color.colorHex if item.color else '#FFFFFF',
'size_name': item.size.name if item.size else 'N/A',
'quantity': item.quantity,
'total_price': item.get_total_price()
}
total_price += item.get_total_price()
else:
# For OrderItem
item_data = {
'id': item.id,
'product_title': item.product.title,
'color_name': item.color.name if item.color else 'N/A',
'color_hex': item.color.colorHex if item.color else '#FFFFFF',
'size_name': item.size.name if item.size else 'N/A',
'quantity': item.quantity,
'total_price': item.get_total_price()
}
total_price += item.get_total_price()
total_quantity += item.quantity
prepared_items.append(item_data)
return {
'items': prepared_items,
'total_quantity': total_quantity,
'total_price': total_price
}
def simulate_payment_success(request):
"""Debug function to simulate successful payment without Stripe"""
# Create an order from the cart
order = create_order(request)
if not order:
messages.error(request, "Could not create order from cart")
return redirect('shop:view_cart')
# Clear the cart
cart.clear_cart(request)
return redirect('shop:payment_success', order_id=order.id)
def simulate_payment_failure(request):
"""Debug function to simulate failed payment without Stripe"""
# Create an order from the cart
order = create_order(request)
if not order:
messages.error(request, "Could not create order from cart")
return redirect('shop:view_cart')
return redirect('shop:payment_cancel', order_id=order.id)
@require_POST
@csrf_exempt
def stripe_webhook(request):
"""Handle Stripe webhook events"""
payload = request.body
sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
# Log the received webhook for debugging
print(f"Webhook received: {sig_header}")
if not sig_header:
print("No signature header")
return HttpResponse(status=400)
try:
# Use the service to verify the webhook
event = stripe_service.verify_webhook_signature(payload, sig_header)
# Log the event type and mode
mode = "TEST" if stripe_service.is_test_mode else "LIVE"
print(f"{mode} webhook event type: {event['type']}")
except ValueError as e:
print(f"Invalid payload: {str(e)}")
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
print(f"Invalid signature: {str(e)}")
return HttpResponse(status=400)
# Handle the event based on type
event_type = event['type']
if event_type == 'checkout.session.completed':
handle_checkout_session_completed(event['data']['object'])
elif event_type == 'payment_intent.succeeded':
handle_payment_intent_succeeded(event['data']['object'])
elif event_type == 'payment_intent.payment_failed':
handle_payment_intent_failed(event['data']['object'])
# Return success response
return HttpResponse(status=200)
def handle_checkout_session_completed(session):
"""Handle completed checkout session webhook"""
print(f"Processing checkout.session.completed for session {session.id}")
# Get order from metadata
order_id = session.get('metadata', {}).get('order_id')
if not order_id:
print(f"Warning: No order_id in metadata for session {session.id}")
return
try:
order = Order.objects.get(id=order_id)
# Update order status if not already processed
if not order.webhook_processed:
print(f"Updating order {order_id} to PAID via webhook")
order.status = OrderStatus.PAID
order.payment_status = 'PAID'
order.webhook_processed = True
order.save()
# You could add additional processing here
# - Send order confirmation email
# - Update inventory
# - Etc.
else:
print(f"Order {order_id} already processed by webhook")
except Order.DoesNotExist:
print(f"Error: Order {order_id} not found for session {session.id}")
def handle_payment_intent_succeeded(payment_intent):
"""Handle successful payment intent webhook"""
print(f"Processing payment_intent.succeeded for intent {payment_intent.id}")
# If you're using payment intents directly, handle them here
# For Checkout Sessions, you'll likely rely on checkout.session.completed instead
def handle_payment_intent_failed(payment_intent):
"""Handle failed payment intent webhook"""
print(f"Processing payment_intent.payment_failed for intent {payment_intent.id}")
# Get order from metadata
order_id = payment_intent.get('metadata', {}).get('order_id')
if not order_id:
print(f"No order_id in metadata for payment intent {payment_intent.id}")
return
try:
order = Order.objects.get(id=order_id)
# Update order status
if not order.webhook_processed or order.payment_status != 'FAILED':
print(f"Updating order {order_id} to FAILED via webhook")
order.status = OrderStatus.CANCELED
order.payment_status = 'FAILED'
order.webhook_processed = True
order.save()
except Order.DoesNotExist:
print(f"Error: Order {order_id} not found for payment intent {payment_intent.id}")