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', 'secondary_color_hex': item.color.secondary_hex_color if item.color and item.color.secondary_hex_color else None, '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', 'secondary_color_hex': item.color.secondary_hex_color if item.color and item.color.secondary_hex_color else None, '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}")