fix: offer/auction checkout — land in the new cart, not a stale empty one
offer_checkout and auction_checkout were creating a cart and hand-setting
cart.active = True, which left the user with two active carts for the
shop; /cart then resolved to the older, empty one — so "Pay $X" appeared
to do nothing. Both now use shop.create_new_cart_for_user() (deactivates
the user's other carts for that shop, activates the new one) and redirect
to /cart/{cart_id} directly, so it works regardless of which shop the
request is scoped to.
Tests: TestOfferCheckout / TestAuctionCheckout now assert there is exactly
one active cart, it carries the cart_offer/cart_auction association, it is
non-empty, and the redirect targets that cart by id. 990 passed.
This commit is contained in:
parent
adf6175d04
commit
5b44132058
3 changed files with 37 additions and 32 deletions
|
|
@ -6623,10 +6623,9 @@ class TestAuctionCheckout(_AuthenticatedBase):
|
|||
self.testapp.get("/log-out")
|
||||
self.log_in_user(self.user2_creds)
|
||||
|
||||
# POST /a/{id}/checkout redirects to /cart.
|
||||
self.testapp.post(f"/a/{auction_id}/checkout", status=302)
|
||||
# POST /a/{id}/checkout redirects into the new cart by id.
|
||||
res = self.testapp.post(f"/a/{auction_id}/checkout", status=302)
|
||||
|
||||
# The user's active cart now has the cart_auction association.
|
||||
from ..models.cart import Cart
|
||||
from ..models.cart_auction import MpsCartAuction
|
||||
from ..models.user import get_or_create_user_by_email
|
||||
|
|
@ -6636,13 +6635,13 @@ class TestAuctionCheckout(_AuthenticatedBase):
|
|||
.filter(Cart.user_id == winner.id, Cart.active.is_(True))
|
||||
.all()
|
||||
)
|
||||
# At least one active cart with an MpsCartAuction row.
|
||||
ca_count = self.dbsession.query(MpsCartAuction).count()
|
||||
self.assertGreaterEqual(ca_count, 1)
|
||||
# That cart's total uses the winning bid amount.
|
||||
match = [c for c in active_carts if c.cart_auctions]
|
||||
self.assertEqual(len(match), 1)
|
||||
self.assertEqual(match[0].total_price_in_cents, 3700)
|
||||
# Exactly one active cart, and it's the auction cart with the
|
||||
# product in it — not a stale empty cart.
|
||||
self.assertEqual(len(active_carts), 1)
|
||||
self.assertTrue(active_carts[0].cart_auctions)
|
||||
self.assertFalse(active_carts[0].is_empty)
|
||||
self.assertEqual(active_carts[0].total_price_in_cents, 3700)
|
||||
self.assertIn(f"/cart/{active_carts[0].uuid_str}", res.location)
|
||||
|
||||
def test_non_winner_checkout_blocked(self):
|
||||
auction_id = self._ended_auction_with_winner()
|
||||
|
|
@ -6734,7 +6733,7 @@ class TestOfferCheckout(_AuthenticatedBase):
|
|||
self.testapp.get("/log-out")
|
||||
self.log_in_user(self.user2_creds)
|
||||
|
||||
self.testapp.post(f"/o/{offer_id}/checkout", status=302)
|
||||
res = self.testapp.post(f"/o/{offer_id}/checkout", status=302)
|
||||
|
||||
from ..models.cart import Cart
|
||||
from ..models.cart_offer import MpsCartOffer
|
||||
|
|
@ -6748,6 +6747,15 @@ class TestOfferCheckout(_AuthenticatedBase):
|
|||
match = [c for c in carts_with_offer if c.cart_offers]
|
||||
self.assertEqual(len(match), 1)
|
||||
self.assertEqual(match[0].total_price_in_cents, 7500)
|
||||
# Regression: the offer cart must be the buyer's ONE active cart
|
||||
# and must contain the product — otherwise /cart lands on a stale
|
||||
# empty cart ("pressing Pay didn't add the item").
|
||||
active_carts = [c for c in carts_with_offer if c.active]
|
||||
self.assertEqual(len(active_carts), 1)
|
||||
self.assertTrue(active_carts[0].cart_offers)
|
||||
self.assertFalse(active_carts[0].is_empty)
|
||||
# ...and the redirect lands on that cart by id.
|
||||
self.assertIn(f"/cart/{match[0].uuid_str}", res.location)
|
||||
|
||||
def test_non_buyer_checkout_blocked(self):
|
||||
offer_id = self._accepted_offer()
|
||||
|
|
|
|||
|
|
@ -298,7 +298,6 @@ def auction_checkout(request):
|
|||
Cart total is overridden to auction.winning_bid amount via the
|
||||
cart_auction association (Cart.auction_offer_override_in_cents).
|
||||
"""
|
||||
from ..models.cart import Cart
|
||||
from ..models.cart_auction import MpsCartAuction
|
||||
|
||||
auction = get_auction_by_id(
|
||||
|
|
@ -319,16 +318,13 @@ def auction_checkout(request):
|
|||
request.session.flash(("Only the auction winner can check out.", "error"))
|
||||
return HTTPFound(f"/a/{auction.uuid_str}")
|
||||
|
||||
# Build a fresh cart for this auction. The product is added with
|
||||
# quantity=auction.quantity so update_inventory() (cart success hook)
|
||||
# naturally deducts the right number of units from inventory.
|
||||
# Cart total comes from auction.winning_bid (override), not the
|
||||
# quantity × product.price computation.
|
||||
cart = Cart(user=request.user)
|
||||
cart.shop = auction.shop
|
||||
cart.active = True
|
||||
request.dbsession.add(cart)
|
||||
request.dbsession.flush()
|
||||
# Build a fresh active cart for this auction (create_new_cart_for_user
|
||||
# deactivates the user's other carts for this shop and activates this
|
||||
# one — a hand-set cart.active = True left two active carts and /cart
|
||||
# resolved to the wrong, empty one). The product is added with
|
||||
# quantity=auction.quantity so update_inventory() deducts the right
|
||||
# number of units; cart total comes from the cart_auction override.
|
||||
cart = auction.shop.create_new_cart_for_user(request.user)
|
||||
cart.add_product(auction.product)
|
||||
if auction.quantity > 1:
|
||||
cart.set_product_quantity(auction.product, auction.quantity)
|
||||
|
|
@ -336,4 +332,4 @@ def auction_checkout(request):
|
|||
MpsCartAuction(cart=cart, auction=auction)
|
||||
)
|
||||
request.dbsession.flush()
|
||||
return HTTPFound("/cart")
|
||||
return HTTPFound(f"/cart/{cart.uuid_str}")
|
||||
|
|
|
|||
|
|
@ -341,11 +341,10 @@ def offer_withdraw(request):
|
|||
@view_config(route_name="offer_checkout", request_method="POST")
|
||||
@user_required(flash_msg="Please log in to pay.")
|
||||
def offer_checkout(request):
|
||||
"""Buyer checkout for an accepted offer: build a cart linked to the
|
||||
offer, redirect to /cart. Cart total is overridden to the agreed
|
||||
"""Buyer checkout for an accepted offer: build a fresh cart linked to
|
||||
the offer and redirect into it. Cart total is overridden to the agreed
|
||||
amount via the cart_offer association.
|
||||
"""
|
||||
from ..models.cart import Cart
|
||||
from ..models.cart_offer import MpsCartOffer
|
||||
from ..models.offer import OFFER_STATE_ACCEPTED
|
||||
|
||||
|
|
@ -359,15 +358,17 @@ def offer_checkout(request):
|
|||
request.session.flash(("Only the buyer can check out.", "error"))
|
||||
return HTTPFound(f"/o/{offer.uuid_str}")
|
||||
|
||||
cart = Cart(user=request.user)
|
||||
cart.shop = offer.shop
|
||||
cart.active = True
|
||||
request.dbsession.add(cart)
|
||||
request.dbsession.flush()
|
||||
# create_new_cart_for_user flips every other cart for this user+shop
|
||||
# to inactive and activates the new one — so /cart resolves to it.
|
||||
# (Setting cart.active = True by hand left two active carts and /cart
|
||||
# would land on the old empty one.)
|
||||
cart = offer.shop.create_new_cart_for_user(request.user)
|
||||
cart.add_product(offer.product)
|
||||
request.dbsession.add(MpsCartOffer(cart=cart, offer=offer))
|
||||
request.dbsession.flush()
|
||||
return HTTPFound("/cart")
|
||||
# Land directly in the cart by id — works regardless of which shop the
|
||||
# request happens to be scoped to (SaaS domain vs the offer's shop).
|
||||
return HTTPFound(f"/cart/{cart.uuid_str}")
|
||||
|
||||
|
||||
@view_config(route_name="shop_offers", renderer="shop_offers.j2")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue