2026-05-20 02:35:48 +00:00

272 lines
8.9 KiB
Python

from datetime import timedelta
from decimal import Decimal
import re
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from cart.views import get_cart, save_cart
from products.models import Product
from .models import Order, OrderItem
STATUS_SEQUENCE = ['Pending', 'Paid', 'Shipped', 'Delivered']
def _build_timeline(status):
current_index = STATUS_SEQUENCE.index(status) if status in STATUS_SEQUENCE else 0
timeline = []
for idx, step in enumerate(STATUS_SEQUENCE):
state = 'done' if idx < current_index else 'current' if idx == current_index else 'upcoming'
timeline.append({'label': step, 'state': state})
return timeline
def _sync_demo_status(order):
elapsed = timezone.now() - order.created_at
if elapsed >= timedelta(minutes=3):
target = 'Delivered'
elif elapsed >= timedelta(minutes=2):
target = 'Shipped'
elif elapsed >= timedelta(minutes=1):
target = 'Paid'
else:
target = 'Pending'
current_index = STATUS_SEQUENCE.index(order.status) if order.status in STATUS_SEQUENCE else 0
target_index = STATUS_SEQUENCE.index(target)
if target_index > current_index:
order.status = target
order.save(update_fields=['status'])
def _get_checkout_source(request):
buy_now = request.session.get('buy_now', {})
if isinstance(buy_now, dict) and buy_now:
return buy_now, 'buy_now'
return get_cart(request), 'cart'
def _clear_checkout_source(request, source_key):
if source_key == 'buy_now':
request.session.pop('buy_now', None)
request.session.modified = True
else:
save_cart(request, {})
def _validate_phone(phone):
digits = re.sub(r'\D+', '', phone)
return 7 <= len(digits) <= 15
@login_required
def checkout(request):
source_items, source_key = _get_checkout_source(request)
if not source_items:
messages.warning(request, 'Your cart is empty. Add products before checking out.')
return redirect('cart')
cart_products = []
total = Decimal('0')
for id, qty in source_items.items():
try:
product_id = int(id)
quantity = int(qty)
except (TypeError, ValueError):
continue
if quantity <= 0:
continue
product = Product.objects.filter(id=product_id).first()
if not product:
messages.error(request, 'One of the selected products is no longer available.')
_clear_checkout_source(request, source_key)
return redirect('cart')
if quantity > product.stock:
if source_key == 'buy_now':
messages.error(request, f'Only {product.stock} units of {product.name} are available.')
_clear_checkout_source(request, source_key)
return redirect('product_detail', product_id=product.id)
messages.error(request, f'Only {product.stock} units of {product.name} are available.')
return redirect('cart')
product.qty = quantity
product.subtotal = product.display_price * quantity
total += product.subtotal
cart_products.append(product)
if not cart_products:
messages.warning(request, 'No valid products found for checkout.')
_clear_checkout_source(request, source_key)
return redirect('cart')
if request.method == 'POST':
full_name = request.POST.get('full_name', '').strip()
phone = request.POST.get('phone', '').strip()
address = request.POST.get('address', '').strip()
if not full_name or not phone or not address:
messages.error(request, 'Please fill in full name, phone, and address.')
return render(
request,
'order/checkout.html',
{
'products': cart_products,
'total': total,
'delivery': {
'full_name': full_name,
'phone': phone,
'address': address,
},
},
)
if not _validate_phone(phone):
messages.error(request, 'Please enter a valid phone number.')
return render(
request,
'order/checkout.html',
{
'products': cart_products,
'total': total,
'delivery': {
'full_name': full_name,
'phone': phone,
'address': address,
},
},
)
order = Order.objects.create(
user=request.user,
total_price=total,
status='Pending',
full_name=full_name,
phone=phone,
address=address,
)
for product in cart_products:
OrderItem.objects.create(order=order, product=product, quantity=product.qty, price=product.display_price)
product.stock = max(product.stock - product.qty, 0)
product.save(update_fields=['stock'])
_clear_checkout_source(request, source_key)
messages.success(request, 'Order created successfully. Complete payment to finish checkout.')
return redirect('payment', order_id=order.id)
return render(
request,
'order/checkout.html',
{
'products': cart_products,
'total': total,
'delivery': {
'full_name': request.user.get_full_name(),
'phone': '',
'address': '',
},
},
)
GATEWAY_OPTIONS = {
'esewa': {
'name': 'eSewa',
'description': 'Pay securely using eSewa mobile wallet or QR code.',
'button_text': 'Pay with eSewa',
},
'khalti': {
'name': 'Khalti',
'description': 'Complete payment instantly with Khalti.',
'button_text': 'Pay with Khalti',
},
'fonpay': {
'name': 'Fonpay',
'description': 'Use Fonpay to pay from your mobile wallet.',
'button_text': 'Pay with Fonpay',
},
}
@login_required
def payment_page(request, order_id):
order = get_object_or_404(Order, id=order_id, user=request.user)
if order.status == 'Paid':
messages.info(request, 'This order has already been paid.')
return redirect('order_detail', order_id=order.id)
if request.method == 'POST':
method = request.POST.get('payment')
if not method:
messages.error(request, 'Please select a payment method.')
return redirect('payment', order_id=order.id)
method_key = method.lower().replace(' ', '')
if method_key in GATEWAY_OPTIONS:
return redirect('payment_gateway', order_id=order.id, gateway=method_key)
if method == 'Cash on Delivery':
order.payment_method = method
order.status = 'Paid'
order.save(update_fields=['payment_method', 'status'])
messages.success(request, 'Cash on Delivery selected. Your order is confirmed.')
return redirect('success')
messages.error(request, 'Selected payment method is not supported.')
return redirect('payment', order_id=order.id)
return render(request, 'order/payment.html', {'order': order, 'gateways': GATEWAY_OPTIONS})
@login_required
def payment_gateway(request, order_id, gateway):
order = get_object_or_404(Order, id=order_id, user=request.user)
if order.status == 'Paid':
messages.info(request, 'This order has already been paid.')
return redirect('order_detail', order_id=order.id)
gateway_data = GATEWAY_OPTIONS.get(gateway)
if gateway_data is None:
messages.error(request, 'Selected payment gateway is not available.')
return redirect('payment', order_id=order.id)
if request.method == 'POST':
order.payment_method = gateway_data['name']
order.status = 'Paid'
order.save(update_fields=['payment_method', 'status'])
messages.success(request, f'Payment completed through {gateway_data["name"]}.')
return redirect('success')
return render(request, 'order/payment_gateway.html', {'order': order, 'gateway': gateway_data})
def success(request):
return render(request, 'order/success.html')
@login_required
def my_orders(request):
orders = list(Order.objects.filter(user=request.user).order_by('-created_at'))
for order in orders:
_sync_demo_status(order)
return render(request, 'order/my_orders.html', {'orders': orders})
@login_required
def order_detail(request, order_id):
order = get_object_or_404(Order, id=order_id, user=request.user)
_sync_demo_status(order)
timeline = _build_timeline(order.status)
return render(request, 'order/order_detail.html', {'order': order, 'timeline': timeline})