Implement Timeout Refund Workflow
DoorDashSoftware Engineer · OnsiteLast updated
Design and implement a stateful, idempotent refund workflow manager that safely handles gateway timeouts, retries, and duplicate requests.
Problem Statement
In food delivery platforms, orders that are not accepted by a restaurant within a specific timeout must be canceled and automatically refunded. External payment gateways are prone to network timeouts and transient failures. To prevent double refunds and stuck states, your workflow must enforce idempotent state transitions and retry-safety.
Implement the RefundWorkflowManager class to orchestrate order refund requests against a simulated payment gateway.
Class Interface
class GatewayResponse:
SUCCESS = "SUCCESS"
TIMEOUT = "TIMEOUT"
ALREADY_PROCESSED = "ALREADY_PROCESSED"
DECLINED = "DECLINED"
class OrderState:
AWAITING_REFUND = "AWAITING_REFUND"
REFUNDING = "REFUNDING"
REFUNDED = "REFUNDED"
MANUAL_REVIEW = "MANUAL_REVIEW"
class PaymentGateway:
def execute_refund(self, idempotency_key: str, amount_cents: int) -> str:
"""
Returns one of: GatewayResponse.SUCCESS, GatewayResponse.TIMEOUT,
GatewayResponse.ALREADY_PROCESSED, GatewayResponse.DECLINED
"""
pass
class RefundWorkflowManager:
def __init__(self, gateway: PaymentGateway):
pass
def register_order(self, order_id: str, amount_cents: int) -> None:
"""Registers an order eligible for refund."""
pass
def process_refund(self, order_id: str, attempt_id: str) -> str:
"""
Attempts to refund the order. Returns the resulting OrderState.
"""
pass
def get_order_state(self, order_id: str) -> str:
"""Returns the current state of the order."""
pass
Workflow Rules
- Order Registration: Newly registered orders start in
AWAITING_REFUND. - State Transitions:
process_refundmoves anAWAITING_REFUNDorder toREFUNDINGand issuesgateway.execute_refundwith an idempotency key composed deterministically oforder_id(e.g.,refund_{order_id}). Note:attempt_idrepresents the caller's execution attempt and must not change the gateway idempotency key.- If the gateway returns
SUCCESSorALREADY_PROCESSED, the state transitions toREFUNDED. - If the gateway returns
DECLINED, the state transitions toMANUAL_REVIEW. - If the gateway returns
TIMEOUT(or raises an exception), the order remains inREFUNDINGso subsequent retries can re-attempt with the exact same idempotency key.
- Idempotency & Re-entrancy:
- If
process_refundis called on an order that is already inREFUNDEDorMANUAL_REVIEW, it should immediately return that terminal state without calling the gateway. - If
process_refundis called on an order currently inREFUNDING(such as after a previousTIMEOUT), it should re-invokegateway.execute_refundusing the original idempotency key. - Calling
process_refundon an unknownorder_idshould raise aKeyError.
- If
Constraints
amount_cents > 0order_idandattempt_idare non-empty strings of length<= 64.- All state transitions must be deterministic.
Example
# Setup
gateway = MockPaymentGateway() # First call returns TIMEOUT, second returns SUCCESS
manager = RefundWorkflowManager(gateway)
manager.register_order("order_123", 2500)
# Attempt 1: Gateway times out
state1 = manager.process_refund("order_123", "attempt_1")
# state1 -> "REFUNDING"
# Attempt 2: Retry with new attempt ID
state2 = manager.process_refund("order_123", "attempt_2")
# state2 -> "REFUNDED"
# Attempt 3: Duplicate invocation after completion
state3 = manager.process_refund("order_123", "attempt_3")
# state3 -> "REFUNDED" (Gateway not called again)
Hints
Hint 1
Tie the gateway's idempotency key directly to the order rather than the retry attempt so external calls remain strictly deduplicated.
Hint 2
Treat both SUCCESS and ALREADY_PROCESSED as successful completions that safely transition the order to REFUNDED.
Hint 3
Ensure terminal states (REFUNDED, MANUAL_REVIEW) short-circuit immediately without side effects.
Follow-up questions
- How would you prevent race conditions if multiple workers call process_refund concurrently for the same order?
- If a gateway timeout leaves an order in REFUNDING indefinitely, how would you implement an automated reconciliation job to query the gateway status?
- How would you adapt this design to Temporal or Cadence activities and workflows?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.