Recommended preparation

Try first

Frame your answer before reading the reference

Mid-level15 min first pass

Candidate prompt

Design the browser checkout and payment flow for a cart whose prices, discounts, tax, shipping, and stock can change. A customer may double-click Pay, lose the creation response, return from bank authentication, or continue the same checkout in another tab. Assign authority among client state, merchant backend, and payment provider. Explain how stable attempt identity, quote conflicts, status recovery, hosted card fields, accessible errors, and cross-tab signals prevent duplicate charges or false success.

Constraints

  • Server quote sets totals and stock.
  • One payment keeps one attempt identity.
  • Redirects never prove payment success.
  • Card secrets stay inside provider fields.

Helpful before you start

  • Async form state
  • HTTP retry basics
  • Hosted payment fields

What good looks like in a 15-minute first pass

Must cover

  • Authoritative quote version gates every payment attempt.
  • Stable attempt and idempotency identities survive transport uncertainty.

Strong signals

  • Lost responses trigger status reads or same-key retries, never a fresh charge.
  • Accessible errors preserve input; cross-tab messages only accelerate server reconciliation.

Stretch if time

Webhook-led fulfillment and multi-tab supersession without browser authority.

Avoid

Redirect success or tab locks are treated as payment truth.

Need a hint? Three decisions to make
  1. Separate editable checkout input, authoritative quote, payment attempt, and order state.
  2. Reuse attempt and idempotency identities when a creation response is uncertain.
  3. Reconcile redirects, webhooks, and cross-tab observations through merchant server reads.
Practice this exact case

Guided mock requires a tablet or desktop viewport of at least 768px. It will not start automatically.

Requirements

A 15-minute checkout trace
  1. Name the customer outcome
    A customer reviews a cart, enters delivery details, receives a current total, and authorizes one payment. Success means the merchant backend confirms the order; a provider redirect is insufficient. Follow one attempt through double-click, a lost response, bank authentication, and another tab so each decision is observable.
  2. Fix the authority boundary
    The browser owns editable form input and presentation. The merchant backend owns versioned price, discount, tax, shipping, stock, and checkout-to-order relationships. The merchant backend is an explicit frontend contract. The provider owns card collection and payment execution. Provider internals stay a black box; fulfillment is outside frontend scope.
  3. Preserve one payment identity
    On Pay, freeze the accepted quote version and create a clientAttemptId with one idempotencyKey. Disable repeated activation for usability, but expect duplicate events. If the response disappears, read by clientAttemptId; retransmission uses the same immutable payload and key. A new identity is allowed only after merchant status proves a terminal no-charge state: failed, canceled, or quote-conflict.
  4. Reconcile uncertain outcomes
    Submitting can become requires-action, processing, succeeded, failed, or quote-conflict. After bank return, refresh, reconnect, or a sibling-tab message, read merchant attempt and order state. Redirect parameters and SDK callbacks are hints, never proof. A changed quote returns to review with corrected totals and preserved form input.
  5. Close with trust and access
    Hosted provider fields keep PAN and CVC outside application state, logs, analytics, and storage. Validation describes problems in text, links fields, focuses a summary when appropriate, and preserves correctable input. Cross-tab messages prompt refresh, but only server reads settle conflicts. Completion requires confirmed order state.
Checkout boundary decisions
ConcernAuthorityBrowser responsibility
Customer detailsCustomer before submission; server after requestsPreserve input and label errors
Price, discount, tax, shipping, stockVersioned server quoteDisplay quoteId and quoteVersion without recalculating totals
Card number and security codeHosted provider fieldsRequest a reference without reading PAN or CVC
Payment and order outcomeMerchant backend after provider reconciliationRefresh status and render truthful recovery or completion
Rejected alternative: trust the return URL
We reject treating a success parameter, provider callback, or disabled Pay button as proof. Navigation can be replayed, callbacks can precede merchant reconciliation, and buttons do not stop duplicate requests. Those signals can show false completion or hide recovery after a lost response.

Architecture

Ownership from cart to fulfillment
OwnerKeepsDoes not claim
Checkout formContact, shipping, consent, error UI, and hosted-field mountsPayable totals, card secrets, or final payment state
Checkout coordinatorCurrent quote and attempt, cancellation request, recovery, and navigationPermission to replace an uncertain attempt
Merchant backendQuotes, attempt lookup, idempotency, provider mapping, orders, and webhooksPAN or CVC handled by hosted provider fields
Payment providerHosted inputs, authentication, and payment executionMerchant order completion
Authoritative checkout model
Editable fields request a quote. The server returns CheckoutQuote with quoteId and quoteVersion; that pair is frozen into CheckoutAttempt. One clientAttemptId and idempotencyKey identify the intent across retransmission. Browser, redirect, SDK, and BroadcastChannel events only trigger getAttempt or getOrder. A confirmed merchant order alone renders completion and begins fulfillment.
Worked example: authentication return after a lost response
  1. Quote
    Shipping S1 produces quote q_18 version 4. The coordinator displays its server total and stores its identifiers, expiry, and lines. A local preview never changes the payable amount.
  2. Create
    The first Pay intent creates ca_7 and idem_ca_7. The coordinator freezes quote version 4 and the payment-method reference, then submits once. A double-click cannot create another command.
  3. Recover
    The response disappears after server acceptance. The UI shows checking and calls getAttempt with ca_7. A permitted retransmission repeats the unchanged command with idem_ca_7; uncertainty never creates ca_8.
  4. Authenticate
    Attempt ca_7 requires bank action. The adapter opens authentication. On return, the route restores checkoutId and ca_7 and reads server status; provider values cannot confirm the order.
  5. Confirm
    A provider webhook lets the merchant advance order o_31. The next status read reports succeeded. The coordinator renders the receipt and broadcasts an advisory observation.
Checkout authority flow from browser form through merchant backend and hosted payment provider to webhook-confirmed order state.
Authority flow: the browser carries intent, while quote and order truth return through the merchant backend.
Read diagram as text

Text fallback: The form requests a versioned quote. Hosted fields return a payment-method reference. The backend creates one idempotent attempt; authentication and a webhook may follow. The browser reads merchant order status.

Data

Separate editable form state from server facts. CheckoutQuote is a replaceable snapshot, not a client calculation. CheckoutAttempt is durable recovery state and excludes action secrets. CheckoutPhase limits honest UI claims; quote-conflict returns control to review.
type CheckoutPhase =
  | 'quoting'
  | 'ready'
  | 'submitting'
  | 'requires-action'
  | 'processing'
  | 'succeeded'
  | 'failed'
  | 'canceled'
  | 'quote-conflict';

interface CheckoutLineItem {
  productId: string;
  quantity: number;
  unitPriceMinor: number;
  lineTotalMinor: number;
}

interface CheckoutQuote {
  quoteId: string;
  quoteVersion: number;
  currency: string;
  lineItems: CheckoutLineItem[];
  subtotalMinor: number;
  discountMinor: number;
  taxMinor: number;
  shippingMinor: number;
  totalMinor: number;
  expiresAt: string;
}

interface CheckoutAttempt {
  checkoutId: string;
  clientAttemptId: string;
  idempotencyKey: string;
  quoteId: string;
  quoteVersion: number;
  phase: CheckoutPhase;
  orderId: string | null;
  failureCode: string | null;
  updatedAt: string;
}

interface CheckoutFormState {
  email: string;
  shippingAddress: Record<string, string>;
  shippingOptionId: string | null;
  fieldErrors: Record<string, string>;
}

interface CheckoutViewState {
  form: CheckoutFormState;
  quote: CheckoutQuote | null;
  attempt: CheckoutAttempt | null;
  phase: CheckoutPhase;
}
Identity and authority matrix
IdentityReuse ruleReason
quoteId + quoteVersionFreeze for one attempt; replace with a new quoteThe server can reject stale totals or availability
clientAttemptIdReuse while recovering the same Pay intentA lost response remains recoverable
idempotencyKeyReuse with the identical attempt payloadTransport retry cannot duplicate provider work
orderIdAccept only from merchant statusA return URL cannot imply an order
Sensitive data is deliberately absent
Durable models exclude PAN, CVC, provider field values, and action tokens. Hosted fields yield an opaque paymentMethodReference. The adapter consumes required-action tokens directly from responses; persistence and telemetry redact them. Sensitive values never enter form state, replay, analytics, URLs, browser storage, or cross-tab messages.

Interfaces

The gateway exposes quote, creation, status, cancellation, and order operations without leaking provider objects into views. clientAttemptId enables lookup; idempotencyKey deduplicates commands. Required-action tokens exist only in transient responses and go directly to HostedPaymentFieldAdapter.
interface CreateAttemptInput {
  checkoutId: string;
  quoteId: string;
  quoteVersion: number;
  clientAttemptId: string;
  idempotencyKey: string;
  paymentMethodReference: string;
  returnUrl: string;
  signal: AbortSignal;
}

interface AttemptStatusResponse {
  attempt: CheckoutAttempt;
  requiredAction: { providerActionToken: string; expiresAt: string } | null;
}

interface OrderStatus {
  orderId: string;
  checkoutId: string;
  status: 'pending-payment' | 'confirmed' | 'payment-failed';
  updatedAt: string;
}

interface CheckoutGateway {
  getQuote(input: { checkoutId: string; signal: AbortSignal }): Promise<CheckoutQuote>;
  createAttempt(input: CreateAttemptInput): Promise<AttemptStatusResponse>;
  getAttempt(input: { checkoutId: string; clientAttemptId: string; signal: AbortSignal }): Promise<AttemptStatusResponse>;
  cancelAttempt(input: { checkoutId: string; clientAttemptId: string; signal: AbortSignal }): Promise<CheckoutAttempt>;
  getOrder(input: { checkoutId: string; orderId: string; signal: AbortSignal }): Promise<OrderStatus>;
}

interface HostedPaymentFieldAdapter {
  mount(input: { container: HTMLElement; onError(message: string): void }): void;
  createPaymentMethodReference(input: { signal: AbortSignal }): Promise<string>;
  handleRequiredAction(input: { providerActionToken: string; signal: AbortSignal }): Promise<void>;
  unmount(): void;
}

type CrossTabCheckoutEvent =
  | {
      type: 'checkout.quote-observed';
      checkoutId: string;
      sourceTabId: string;
      quoteVersion: number;
      observedAt: string;
    }
  | {
      type: 'checkout.attempt-observed';
      checkoutId: string;
      sourceTabId: string;
      clientAttemptId: string;
      phase: CheckoutPhase;
      observedAt: string;
    };
Recovery-oriented request semantics
OperationRequired identityClient behavior
GET current quotecheckoutIdReplace the snapshot; require review after a version change
POST payment attemptquoteId, quoteVersion, clientAttemptId, and Idempotency-KeyRetain the command until server state is known
GET payment attemptcheckoutId and clientAttemptIdRecover timeout, redirect, reconnect, or tab observations; consume action tokens transiently
POST cancel attemptcheckoutId and clientAttemptIdReplace only after canceled; otherwise keep recovering the old attempt
GET orderMerchant-issued checkoutId and orderIdComplete only when confirmed
Request and reconciliation path
  1. Quote before authorization
    Fetch after material cart or shipping changes. Cancel superseded reads, accept only the current input generation, and display returned totals exactly.
  2. Create through one command
    Get an opaque hosted-field reference, freeze the quote pair, generate identities once, and send Idempotency-Key. Retain the command while its outcome is uncertain.
  3. Read before retry
    After timeout, call getAttempt. A permitted retransmission repeats the same payload and key. Replace it only after merchant status reports failed, canceled, or quote-conflict, including successful cancelAttempt.
  4. Restore after navigation
    The return route carries identities, not success. It reads attempt state, sends action tokens directly to the adapter, and reads order state after a merchant-issued orderId appears.
Accessible field-error contract
Return stable field keys and readable messages. Put a text summary before the fields. The form links each summary item to its control, sets programmatic associations, and focuses the summary after submit failure. Preserve valid values. Adapter errors name the hosted control without exposing its value.

Optimizations

Failure response matrix
FailureRecoveryPreserved invariant
Quote changes before PayReplace the quote and require reviewOnly a current server quote enters an attempt
Attempt response is lostRead by clientAttemptId; permitted retry keeps the same Idempotency-Key and payloadOne intent maps to at most one provider operation
Authentication return arrives firstShow checking and refresh merchant stateRedirect and SDK results never prove success
Attempt remains processingUse bounded, visibility-aware polling plus manual refreshThe UI stays truthful without another charge
Customer requests another paymentRead the old attempt; replace only after failed, canceled, or quote-conflictProcessing cannot become an unexpected second charge
Provider field rejects inputKeep input and present linked text errorsRecovery stays accessible; card secrets remain excluded

Cross-tab reconciliation without browser authority

BroadcastChannel says state may have changed. Siblings ignore their own sourceTabId and refresh by checkoutId; they never copy a sender's phase into confirmed UI. Missing or reordered delivery is safe because the channel is advisory and the server remains the concurrency boundary.
Scenario walkthrough: two tabs diverge
  1. Older tab leaves for authentication
    Tab A creates ca_7 on quote version 4 and leaves for authentication. Its identity remains recoverable.
  2. Newer tab changes shipping
    Tab B gets quote version 5 but cannot replace uncertain ca_7. It proceeds only after merchant status reports failed, canceled, or quote-conflict.
  3. Return reconciles both identities
    Tab A reads ca_7 and the quote. A confirmed order stops Tab B; failure or quote-conflict resumes review.
  4. Advisory event speeds convergence
    An attempt observation prompts refresh. Crashes strand local locks, and browsers cannot settle webhook truth.
Two checkout tabs reconcile an older authenticated attempt and a newer quote by reading authoritative merchant server status.
BroadcastChannel prompts refresh; merchant reads decide current quote, attempt, and order.
Read diagram as text

Text fallback: Tab A returns for ca_7 while Tab B holds a newer quote. Both read merchant state. Confirmation stops competing work; failure restores review.

Verification, accessibility, and measurement

Tests tied to customer trust
  • Replay Pay; assert one clientAttemptId, one Idempotency-Key, and one merchant attempt.
  • Lose creation response; assert lookup or same-key retry reaches the existing attempt.
  • Return before webhook reconciliation; assert processing remains and no receipt appears.
  • Request replacement while processing; assert no new identity before terminal state or successful cancellation.
  • Change quote version; assert review is required and correctable input survives.
  • Reorder cross-tab events; assert receivers refresh instead of copying browser claims.
  • Check keyboard and screen-reader recovery, linked errors, focus, preserved values, and redacted telemetry.
Measure duplicate suppression, uncertainty duration, quote conflicts, authentication abandonment, polling, cancellation, and recovery. Segment by provider, lifecycle, and network before tuning refresh. Logs keep correlation IDs but exclude hosted-field values and secrets. A kill switch stops initiation without hiding existing status.

Use the Question Library for baseline coverage, then move into a Study Plan before targeted Company Prep.