POST /api/v1/checkout/ always converted the customer's entire multi-store cart into orders. Add an optional store_uuids field to CheckoutSerializer — when given, checkout() only processes those stores' cart groups and only clears their items, leaving the rest of the cart intact for a later checkout. Omitted, behavior is unchanged (whole cart checks out). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
101 lines
4.5 KiB
Python
101 lines
4.5 KiB
Python
from apps.orders.models import Cart, Order, OrderGroup
|
|
|
|
from .base import OrdersTestCase
|
|
|
|
|
|
class CheckoutTests(OrdersTestCase):
|
|
def setUp(self):
|
|
self.mock_external_clients()
|
|
self.client.force_authenticate(user=self.customer)
|
|
|
|
def test_checkout_splits_multi_store_cart_into_one_order_per_store(self):
|
|
self.add_to_cart(self.product1, quantity=2)
|
|
self.add_to_cart(self.product2, quantity=1)
|
|
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.CASH_ON_DELIVERY,
|
|
})
|
|
|
|
self.assertEqual(response.status_code, 201, response.data)
|
|
group = OrderGroup.objects.get(uuid=response.data['uuid'])
|
|
self.assertEqual(group.orders.count(), 2)
|
|
|
|
order1 = group.orders.get(store=self.store1)
|
|
self.assertEqual(order1.items_subtotal, 28_500 * 2)
|
|
self.assertEqual(order1.delivery_fee, 15_000)
|
|
self.assertEqual(order1.commission_amount, round(57_000 * 0.04))
|
|
self.assertEqual(order1.seller_payout_amount, 57_000 - order1.commission_amount)
|
|
self.assertEqual(order1.status, Order.Status.PLACED)
|
|
|
|
# Cart is cleared and stock decremented after a successful checkout.
|
|
self.assertFalse(Cart.objects.get(user=self.customer).items.exists())
|
|
self.product1.refresh_from_db()
|
|
self.assertEqual(self.product1.stock_quantity, 48)
|
|
|
|
def test_checkout_can_be_limited_to_selected_stores(self):
|
|
self.add_to_cart(self.product1, quantity=1)
|
|
self.add_to_cart(self.product2, quantity=1)
|
|
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.CASH_ON_DELIVERY,
|
|
'store_uuids': [str(self.store1.uuid)],
|
|
})
|
|
|
|
self.assertEqual(response.status_code, 201, response.data)
|
|
group = OrderGroup.objects.get(uuid=response.data['uuid'])
|
|
self.assertEqual(group.orders.count(), 1)
|
|
self.assertEqual(group.orders.get().store, self.store1)
|
|
|
|
# store2's cart item is left untouched for a later checkout.
|
|
remaining_cart = Cart.objects.get(user=self.customer)
|
|
self.assertEqual(remaining_cart.items.count(), 1)
|
|
self.assertEqual(remaining_cart.items.get().product, self.product2)
|
|
|
|
def test_checkout_rejects_store_uuid_not_in_cart(self):
|
|
self.add_to_cart(self.product1, quantity=1)
|
|
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.CASH_ON_DELIVERY,
|
|
'store_uuids': [str(self.store2.uuid)],
|
|
})
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertTrue(Cart.objects.get(user=self.customer).items.exists())
|
|
|
|
def test_checkout_rejects_empty_cart(self):
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.CASH_ON_DELIVERY,
|
|
})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_checkout_rejects_insufficient_stock(self):
|
|
self.add_to_cart(self.product1, quantity=999)
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.CASH_ON_DELIVERY,
|
|
})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_checkout_rejects_address_outside_store_coverage(self):
|
|
self.store1.service_neighborhoods.set([self.other_neighborhood])
|
|
self.add_to_cart(self.product1, quantity=1)
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.CASH_ON_DELIVERY,
|
|
})
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_checkout_leaves_payment_status_pending_regardless_of_method(self):
|
|
"""No payment integration on this branch — see feature/payment."""
|
|
self.add_to_cart(self.product1, quantity=1)
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.WALLET,
|
|
})
|
|
self.assertEqual(response.status_code, 201, response.data)
|
|
group = OrderGroup.objects.get(uuid=response.data['uuid'])
|
|
self.assertEqual(group.payment_status, OrderGroup.PaymentStatus.PENDING)
|