From ebeb8afa87fb23615b1d2496baeb780f253b40f1 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Wed, 13 May 2026 06:28:18 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20offer=20page=20=E2=80=94=20seller=20sees?= =?UTF-8?q?=20a=20shareable=20pay=20link,=20buyer=20pay=20button=20stands?= =?UTF-8?q?=20out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an offer is accepted but unpaid, the seller's view now has an "Awaiting payment" block: the buyer's name (linked to their profile), the agreed amount, and the offer URL pre-filled in a read-only input plus a "Copy link" button (capability-driven — the input is selectable for manual copy when JS is absent or clipboard API is blocked). The notice banner also says "waiting on the buyer to pay $X" in the seller's view. The buyer's pay-now button is now mps-button-green so it's unmistakable as the call-to-action. Tests: TestOfferCheckout gains two render tests — seller sees the awaiting-payment block + URL but not a pay-now form; buyer sees the $75.00 pay-now form but not the awaiting block. 9 in class pass. --- make_post_sell/static/css/common.css | 16 +++++++++++++ make_post_sell/templates/offer.j2 | 30 ++++++++++++++++++------- make_post_sell/tests/test_functional.py | 20 +++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index a39b59e..1c7cdfa 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -1635,6 +1635,22 @@ div.edit-page > section.edit-card-full { border-radius: var(--radius-md, 8px); } +/* Accepted-offer pay-link row shown to the seller: an input pre-filled + with the buyer's pay URL plus a copy-to-clipboard button. */ +.offer-pay-link-row { + display: grid; + grid-template-columns: 1fr auto; + gap: var(--space-2, 8px); + margin-top: var(--space-3, 12px); +} +.offer-pay-link { + width: 100%; + box-sizing: border-box; + padding: var(--space-2, 8px) var(--space-3, 12px); + font-family: var(--font-mono, ui-monospace, monospace); + font-size: var(--text-sm, 0.875rem); +} + /* Design-system settings form: stacked label + control + hint per field, two-up grid on wider viewports. Grid only — no flex. */ .settings-form { diff --git a/make_post_sell/templates/offer.j2 b/make_post_sell/templates/offer.j2 index b0812b3..5ffa80e 100644 --- a/make_post_sell/templates/offer.j2 +++ b/make_post_sell/templates/offer.j2 @@ -46,7 +46,7 @@ {% elif is_accepted %}
-

Offer accepted{% if request.user and request.user.uuid_str == buyer_id %} — pay now to complete your purchase{% endif %}.

+

Offer accepted{% if request.user and request.user.uuid_str == buyer_id %} — pay now to complete your purchase{% elif not is_paid %} — waiting on the buyer to pay ${{ "%.2f"|format(current_amount) }}{% endif %}.

{% elif can_act %}
@@ -97,13 +97,27 @@ {% endif %} - {# Pay-now CTA when the offer is accepted and the viewer is the buyer. #} - {% if is_accepted and request.user and request.user.uuid_str == buyer_id %} -
-
- -
-
+ {# Accepted-but-not-paid: the buyer gets the pay-now CTA; the seller + (or shop editor) gets a shareable link to send the buyer, since the + seller cannot pay on the buyer's behalf. #} + {% if is_accepted and not is_paid %} + {% if request.user and request.user.uuid_str == buyer_id %} +
+
+ +
+
+ {% elif request.user %} +
+

Awaiting payment

+

You accepted {{ buyer_name }}’s offer at ${{ "%.2f"|format(current_amount) }}. They need to sign in and pay from this same page — send them the link:

+ +
+ {% endif %} {% endif %}
diff --git a/make_post_sell/tests/test_functional.py b/make_post_sell/tests/test_functional.py index 1411783..4634eda 100644 --- a/make_post_sell/tests/test_functional.py +++ b/make_post_sell/tests/test_functional.py @@ -6810,6 +6810,26 @@ class TestOfferCheckout(_AuthenticatedBase): # ...and the redirect lands on that cart by id. self.assertIn(f"/cart/{match[0].uuid_str}", res.location) + def test_accepted_offer_seller_sees_pay_link_to_share(self): + offer_id = self._accepted_offer() + # _accepted_offer leaves user1 (seller / shop owner) logged in. + body = self.testapp.get(f"/o/{offer_id}", status=200).body.decode() + self.assertIn("Awaiting payment", body) + self.assertIn("offer-pay-link", body) + self.assertIn("waiting on the buyer to pay $75.00", body) + # Seller never sees the buyer's pay-now form (they cannot pay). + self.assertNotIn(f"/o/{offer_id}/checkout", body) + + def test_accepted_offer_buyer_sees_pay_now(self): + offer_id = self._accepted_offer() + self.testapp.get("/log-out") + self.log_in_user(self.user2_creds) + body = self.testapp.get(f"/o/{offer_id}", status=200).body.decode() + self.assertIn(f"/o/{offer_id}/checkout", body) + self.assertIn("Pay $75.00 now", body) + # Buyer view does not show the seller's awaiting-payment block. + self.assertNotIn("Awaiting payment", body) + def test_non_buyer_checkout_blocked(self): offer_id = self._accepted_offer() # Logged in as user1 (seller). Should be blocked.