Skip to content

feat: [CIT-5181] show real copy when the basket is locked by an in-flight checkout - #7266

Draft
JoeTravisKiva wants to merge 2 commits into
mainfrom
CIT-5181
Draft

JoeTravisKiva wants to merge 2 commits into
mainfrom
CIT-5181

Conversation

@JoeTravisKiva

@JoeTravisKiva JoeTravisKiva commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

CIT-5181 — follow-up to CIT-4190 / CIT-5194.

Why

The backend now refuses basket edits while the lender's own checkout is still running. That's correct, but it turned a silent data corruption into a visible failure, and the lender got generic error copy.

Worse, the add-to-basket paths routed these failures through hasBasketExpired()handleInvalidBasket(), which deletes the kvbskt cookie and reloads the page. The basket is busy, not broken, so that's the wrong response. Avoiding exactly that is why CIT-5194 stopped these failures reporting shop.invalidBasketId.

Two codes mean the same thing and are treated identically:

Code Source
checkout_in_progress loan reservation path (updateLoanReservation)
shop.checkoutInProgress donation, Kiva Card and credit paths

What

Three helpers in src/util/basketUtils.js, next to the existing hasBasketExpired:

  • isCheckoutInProgress(code) — matches both codes
  • getBasketErrorCode(error) — reads extensions.code, falls back to code
  • getBasketErrorMessage(error) — lender-facing copy, overriding the backend string

Wired through add-to-basket (borrower-profile-exp-mixin, KvClassicLoanCardContainer, KivaClassicBasicLoanCard, LendButton), resize (LoanPrice), remove (RemoveBasketItem), donation (DonationItem) and checkout-utils-mixin's showCheckoutError.

Notes for review

  • The copy is a placeholder. It's the engineer-written backend string, pending design/content. It lives in one constant (CHECKOUT_IN_PROGRESS_MESSAGE) so swapping it is a one-line change. The lock self-heals after 300s, so the copy deliberately doesn't imply a permanent state.
  • Sentry is skipped for these codes where the call site owns the reporting — expected contention, not a defect. Note setLendAmount captures before it rejects, so its two callers still report regardless; only the redundant second capture is skipped.
  • Drive-by fix: LoanPrice.vue and RemoveBasketItem.vue destructured a top-level code off GraphQL errors, where the code actually lives under extensions.code. Their not_all_shared_added checks have therefore never fired. Routing through getBasketErrorCode fixes that, which means that branch (an 8s delayed reload) can now actually trigger.

Testing

332 test files / 4991 tests passing, lint clean. New specs cover both codes at each call site, each paired with a control asserting an expired-basket error still clears the basket — so the tests prove the branch discriminates rather than always passing.

Deliberately out of scope

  • Disabling basket edit controls during checkout (the ticket's second half). Needs checkoutTransactionId/checkoutRequestedAt plumbed into the basket queries — nothing queries them today — plus a freshness check against the 300s TTL.
  • Consolidating the four add-to-basket handlers. Two are byte-identical and eb62855ca was a one-string fix applied across all four, so the pull is real — but extraction would push $showTipMsg/$kvTrackEvent into basketUtils.js, which today takes only data. Wants a useAddToBasket() composable and its own ticket.
  • Remaining call sites that still show the placeholder: AdaptiveMicroLoanCard, AppealBannerCircularContainer, CheckoutPage:1293, DepositIncentiveUpsell, LendCta, CCLandingPage. ProcessInstantLending and LendButton2 additionally Sentry-capture, so contention there will page someone.
  • TeamAttribution / DonateRepaymentsToggle hit updateLoanReservationTeam / updateLoanReservationDonateRepayments — pending backend confirmation on whether those take the same lock.

🤖 Generated with Claude Code

The backend now refuses basket edits while the lender's own checkout is
running (CIT-4190), returning checkout_in_progress from the loan
reservation path or shop.checkoutInProgress from the donation, Kiva Card
and credit paths. Both surfaced as generic error copy, and the add-to-
basket paths routed them through handleInvalidBasket, which deletes the
basket cookie and reloads -- wrong here, since the basket is busy rather
than broken.

Adds isCheckoutInProgress, getBasketErrorCode and getBasketErrorMessage
to basketUtils, and wires them through the add-to-basket, resize, remove
and donation paths. The copy is a placeholder pending design/content
review; it lives in one constant so swapping it is a one-line change.

Skips Sentry for these codes where the call site owns the reporting --
expected contention, not a defect. Note setLendAmount captures before it
rejects, so its two callers still report.

Also fixes a latent bug in LoanPrice and RemoveBasketItem, which read a
top-level `code` off GraphQL errors where the code lives under
extensions.code, so their not_all_shared_added checks never fired.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JoeTravisKiva JoeTravisKiva changed the title CIT-5181: show real copy when the basket is locked by an in-flight checkout feat: [CIT-5181] show real copy when the basket is locked by an in-flight checkout Sep 16, 2026
# Conflicts:
#	test/unit/specs/components/Checkout/DonationItem.spec.js

@mcstover mcstover left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good and covers the stuff in this repo so a different PR could hold the updates from kv-shop

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants