From 3c8dbca7c6c5beff6378a80a1ae7e722edea12ce Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Thu, 14 May 2026 17:36:44 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20PayPal=20+=20Adyen=20complete=20paths=20?= =?UTF-8?q?flip=20offer.state=20ACCEPTED=20=E2=86=92=20PAID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A negotiated cart paid through Stripe's user_cart_complete_checkout already fired _finalize_auction_offer_state(cart, request) — which flips linked offers to PAID and linked auctions to SETTLED. But the PayPal complete-checkout (cart.py:paypal_complete_checkout) and the Adyen complete-checkout (cart.py:adyen_complete_checkout) paths never called it. Symptom: buyer pays a negotiated offer through PayPal or Adyen, invoice is written, sale email fires — but offer.state stays at ACCEPTED. The offer detail page keeps rendering the Pay $X / Cancel buttons because `{% if is_accepted and not is_paid %}` is still True. Even worse, the buyer could click Cancel after the payment had processed (the cancel endpoint guarded on `state != ACCEPTED`, which also still permitted it). Fix: add the _finalize_auction_offer_state(cart, request) call to both paypal_complete_checkout and adyen_complete_checkout, right when successful_invoices is populated — before the cart line items get drained. Each takes (cart, request) which both sites have in scope. With the offer now in PAID: - is_paid=True flips the offer.j2 gate, hiding Pay/Cancel. - /o/{id}/cancel raises OfferRejected ("only accepted offers can be cancelled by buyer"). - _user_party / shop_offers / buyer dashboards all read the right terminal state. Note: this only patches the cart.py callsites. Webhook fallbacks (views/webhooks.py) and the crypto-watcher finalize path (lib/crypto_watcher/__init__.py) still don't call mark_paid because they only have `invoice`, not `cart`. Follow-up will route those through a `_finalize_invoice_negotiation(invoice, session)` helper once that's needed; the cart.py paths cover the common case. --- make_post_sell/views/cart.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/make_post_sell/views/cart.py b/make_post_sell/views/cart.py index d3a23b2..7eaf968 100644 --- a/make_post_sell/views/cart.py +++ b/make_post_sell/views/cart.py @@ -1069,6 +1069,13 @@ def paypal_complete_checkout(request): continue if successful_invoices: + # MPS-20 + MPS-21: flip linked auction/offer state BEFORE + # the cart_offers / cart_auctions get touched downstream. + # Without this, a PayPal- or Adyen-paid negotiated cart + # leaves the offer stuck in ACCEPTED and the UI keeps + # showing Pay/Cancel even though the invoice is settled. + _finalize_auction_offer_state(cart, request) + for invoice in successful_invoices: for line_item in invoice.line_items: cart.remove_product(line_item.product, line_item.quantity) @@ -1304,6 +1311,13 @@ def adyen_complete_checkout(request): continue if successful_invoices: + # MPS-20 + MPS-21: flip linked auction/offer state BEFORE + # the cart_offers / cart_auctions get touched downstream. + # Without this, a PayPal- or Adyen-paid negotiated cart + # leaves the offer stuck in ACCEPTED and the UI keeps + # showing Pay/Cancel even though the invoice is settled. + _finalize_auction_offer_state(cart, request) + for invoice in successful_invoices: for line_item in invoice.line_items: cart.remove_product(line_item.product, line_item.quantity)