fix: offer page — seller sees a shareable pay link, buyer pay button stands out
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.
This commit is contained in:
parent
8b40e90d58
commit
ebeb8afa87
3 changed files with 58 additions and 8 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
{% elif is_accepted %}
|
||||
<div class="alert alert-success offer-state-notice" name="alert">
|
||||
<p class="alert-message">Offer accepted{% if request.user and request.user.uuid_str == buyer_id %} — pay now to complete your purchase{% endif %}.</p>
|
||||
<p class="alert-message">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 %}.</p>
|
||||
</div>
|
||||
{% elif can_act %}
|
||||
<div class="alert alert-info offer-state-notice" name="alert">
|
||||
|
|
@ -97,13 +97,27 @@
|
|||
</section>
|
||||
{% 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 %}
|
||||
<section class="well">
|
||||
<form method="post" action="/o/{{ id }}/checkout">
|
||||
<input type="submit" class="mps-submit" value="Pay ${{ "%.2f"|format(current_amount) }} now" />
|
||||
</form>
|
||||
</section>
|
||||
{# 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 %}
|
||||
<section class="well offer-pay-cta">
|
||||
<form method="post" action="/o/{{ id }}/checkout">
|
||||
<input type="submit" class="mps-submit mps-button-green" value="Pay ${{ "%.2f"|format(current_amount) }} now" />
|
||||
</form>
|
||||
</section>
|
||||
{% elif request.user %}
|
||||
<section class="well offer-await-pay">
|
||||
<h3 class="type-title">Awaiting payment</h3>
|
||||
<p>You accepted <a href="/profile/{{ buyer_handle }}?shop={{ shop_id }}">{{ buyer_name }}</a>’s offer at <strong>${{ "%.2f"|format(current_amount) }}</strong>. They need to sign in and pay from this same page — send them the link:</p>
|
||||
<div class="offer-pay-link-row">
|
||||
<input type="text" readonly class="offer-pay-link" value="{{ request.scheme }}://{{ request.host }}/o/{{ id }}" onclick="this.select();" />
|
||||
<button type="button" class="mps-button-small js-only"
|
||||
onclick="if(navigator.clipboard){navigator.clipboard.writeText(this.previousElementSibling.value);this.textContent='Copied!';}else{this.previousElementSibling.select();}">Copy link</button>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<section class="offer-timeline well">
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue