Debug a Payment Service's Charge Flow Against Retries, Crashes and Timeouts

Read the full interview experience this question came from →

Quick Overview

A debugging exercise on a payment service that charges users through an external provider and then records the result. You find how caller retries, crashes and timeouts can double-charge a user or lose a payment, then restructure the flow. It tests idempotency, payment state machines and reconciliation of unknown outcomes.

Debug a Payment Service's Charge Flow Against Retries, Crashes and Timeouts

Company: Mercor

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

You are asked to debug the charge flow of a payment service. The service charges a user for an order through an external payment provider and records the payment in its own database. The original code from the interview was not reported; the version below is a representative reconstruction of the flow under review. ```java public class PaymentService { private final PaymentProvider provider; // client for the external payment provider private final PaymentRepository payments; // our database public PaymentService(PaymentProvider provider, PaymentRepository payments) { this.provider = provider; this.payments = payments; } /** Charges the user for the order and records the result. */ public PaymentStatus pay(String orderId, String userId, long amountMinor) { ChargeResult result = provider.charge(userId, amountMinor); // network call payments.insert(new Payment(orderId, userId, amountMinor, result.status())); return result.status(); } } ``` Context for the reconstruction: - `pay` is called through an HTTP endpoint, and its callers retry when a call fails or times out. - `provider.charge` returns a result whose status is either succeeded or declined, and throws on network errors, timeouts and provider server errors. - `amountMinor` is the amount in the currency's smallest unit, such as cents. Find the ways this flow can charge a user twice or lose track of a payment, and restructure it so that it cannot. ### Clarifying Questions - Does the provider accept an idempotency key on charge requests? If so, for how long does it remember a key? - Can the provider be asked for the outcome of a charge by a reference we supply, and does it send webhooks? - What identifies one logical payment from the caller's side: the order ID, or a request ID the client generates once per payment attempt? - What should the caller be told while the outcome of a charge is still unknown? ### Part 1 — Failure scenarios Walk through the concrete sequences of events in which the current code charges a user twice or leaves a charge with no record. ```hint Interrupt it everywhere Consider a crash, a lost response or a timeout at each point in the flow, and what the caller does next in each case. ``` #### What This Part Should Cover - Caller retries after a lost or slow response - A crash or database failure between the provider call and the insert - A provider call whose outcome is unknown - Concurrent duplicate requests for the same payment ### Part 2 — A safe charge flow Restructure the flow so that retries and duplicate requests can never produce a second charge, and a crash at any point leaves a record you can act on. ```hint Record intent before acting Think about what your database should already contain at the moment the provider is called, and what key would let a repeated request recognize the first one. ``` #### What This Part Should Cover - The deduplication mechanism, where its key comes from, and how the database enforces it - The order of the database writes and the provider call, and the states a payment moves through - Safe state transitions when several components may update the same payment - What the caller receives in each case ### Part 3 — Outcomes you cannot know The provider call times out. What state does the payment go into, and what eventually turns it into a final answer? ```hint Unknown is a state too Rather than guessing success or failure inside the request, consider recording that the outcome is not yet known, and decide which component, using what information, can settle it later. ``` #### What This Part Should Cover - Distinguishing definite failures from failures whose outcome is uncertain - The process that resolves uncertain payments, and what it does for each outcome it finds - Retrying safely, and escalation when the outcome stays unknown - Monitoring for payments stuck in an undecided state ### What a Strong Answer Covers - Every double-charge and lost-record scenario identified with a concrete sequence of events - Deduplication enforced by the database, not only by an application-level check - An intent record written before the external call, and an explicit payment state machine with guarded transitions - Correct handling of ambiguous failures, with a reconciliation path and escalation - The contract with the caller, observability, and the trade-offs of the chosen design ### Follow-up Questions - The provider does not support idempotency keys at all. How do you avoid double charges when a charge must be retried? - A provider webhook and your own background process try to finalize the same payment at the same moment. How do you keep the final state correct? - How long must you keep idempotency keys, and what happens if a client retries after a key has been deleted? - How would you make refunds safe against the same timeout uncertainty?

Overview: A debugging exercise on a payment service that charges users through an external provider and then records the result. You find how caller retries, crashes and timeouts can double-charge a user or lose a payment, then restructure the flow. It tests idempotency, payment state machines and reconciliation of unknown outcomes.

Read the full Mercor Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/Mercor
Mercor logo
Mercor
Oct 4, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
0
0

You are asked to debug the charge flow of a payment service. The service charges a user for an order through an external payment provider and records the payment in its own database. The original code from the interview was not reported; the version below is a representative reconstruction of the flow under review.

public class PaymentService {
    private final PaymentProvider provider;     // client for the external payment provider
    private final PaymentRepository payments;   // our database

    public PaymentService(PaymentProvider provider, PaymentRepository payments) {
        this.provider = provider;
        this.payments = payments;
    }

    /** Charges the user for the order and records the result. */
    public PaymentStatus pay(String orderId, String userId, long amountMinor) {
        ChargeResult result = provider.charge(userId, amountMinor);   // network call
        payments.insert(new Payment(orderId, userId, amountMinor, result.status()));
        return result.status();
    }
}

Context for the reconstruction:

  • pay is called through an HTTP endpoint, and its callers retry when a call fails or times out.
  • provider.charge returns a result whose status is either succeeded or declined, and throws on network errors, timeouts and provider server errors.
  • amountMinor is the amount in the currency's smallest unit, such as cents.

Find the ways this flow can charge a user twice or lose track of a payment, and restructure it so that it cannot.

Clarifying Questions Guidance

  • Does the provider accept an idempotency key on charge requests? If so, for how long does it remember a key?
  • Can the provider be asked for the outcome of a charge by a reference we supply, and does it send webhooks?
  • What identifies one logical payment from the caller's side: the order ID, or a request ID the client generates once per payment attempt?
  • What should the caller be told while the outcome of a charge is still unknown?

Part 1 — Failure scenarios

Walk through the concrete sequences of events in which the current code charges a user twice or leaves a charge with no record.

What This Part Should Cover Guidance

  • Caller retries after a lost or slow response
  • A crash or database failure between the provider call and the insert
  • A provider call whose outcome is unknown
  • Concurrent duplicate requests for the same payment

Part 2 — A safe charge flow

Restructure the flow so that retries and duplicate requests can never produce a second charge, and a crash at any point leaves a record you can act on.

What This Part Should Cover Guidance

  • The deduplication mechanism, where its key comes from, and how the database enforces it
  • The order of the database writes and the provider call, and the states a payment moves through
  • Safe state transitions when several components may update the same payment
  • What the caller receives in each case

Part 3 — Outcomes you cannot know

The provider call times out. What state does the payment go into, and what eventually turns it into a final answer?

What This Part Should Cover Guidance

  • Distinguishing definite failures from failures whose outcome is uncertain
  • The process that resolves uncertain payments, and what it does for each outcome it finds
  • Retrying safely, and escalation when the outcome stays unknown
  • Monitoring for payments stuck in an undecided state

What a Strong Answer Covers Guidance

  • Every double-charge and lost-record scenario identified with a concrete sequence of events
  • Deduplication enforced by the database, not only by an application-level check
  • An intent record written before the external call, and an explicit payment state machine with guarded transitions
  • Correct handling of ambiguous failures, with a reconciliation path and escalation
  • The contract with the caller, observability, and the trade-offs of the chosen design

Follow-up Questions Guidance

  • The provider does not support idempotency keys at all. How do you avoid double charges when a charge must be retried?
  • A provider webhook and your own background process try to finalize the same payment at the same moment. How do you keep the final state correct?
  • How long must you keep idempotency keys, and what happens if a client retries after a key has been deleted?
  • How would you make refunds safe against the same timeout uncertainty?
Loading comments...