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 django.contrib.auth.forms import AuthenticationForm from django.contrib.auth import login from django.contrib.auth.decorators import login_required 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 . 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': order.id, } # Add user info to metadata if available if request.user.is_authenticated: metadata['user_id'] = request.user.id elif 'guest_email' in request.session: metadata['guest_email'] = request.session.get('guest_email', '') try: # Initialize Stripe with your secret key stripe.api_key = settings.STRIPE_SECRET_KEY # Create session checkout_session = stripe.checkout.Session.create( payment_method_types=['card'], line_items=line_items, mode='payment', 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: # Log error and re-raise 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 ) 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 ) except (KeyError, GuestUser.DoesNotExist): # No guest user information, create order without user order = Order.objects.create( total_price=total_price ) # 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_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') # Render payment page return render(request, 'shop/payment.html', { 'order': order, 'order_items': order_items, '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) # Update order status order.status = OrderStatus.PAID order.payment_status = 'PAID' order.save() # Get order items for template order_items = order.items.all() total = sum(item.get_total_price() for item in order_items) display_data = prepare_item_display_data(order_items, is_cart=False) print(display_data) 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) # Update order status order.status = OrderStatus.CANCELED order.payment_status = 'FAILED' order.save() 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') # Set order as paid order.status = OrderStatus.PAID order.payment_status = 'PAID' order.save() # 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') # Set order as canceled order.status = OrderStatus.CANCELED order.payment_status = 'FAILED' order.save() return redirect('shop:payment_cancel', order_id=order.id)