Katkat

Prepare for Backend Parsing, API Integration, AI Coding, and Bug Fixing Rounds

StripeSoftware Engineer · Onsite

Last updated

Tests parsing structured text payloads, managing transactional state machines, and implementing strict idempotency semantics with robust error handling.

Payment Event Stream Processor

At payment platforms like Stripe, ingestion pipelines often receive high-volume streams of semi-structured event logs from external partner rails. These events must be parsed, validated, and applied against an account balance while respecting idempotency and lifecycle constraints.

Implement a function process_payment_events(events: list[str]) -> dict that parses a sequence of payment action records, validates each record against business rules, and returns the processing results.

Record Format

Each string in events follows the format: ACTION|idempotency_key=;amount=;currency=;charge_id=

  • Supported actions: AUTHORIZE, CAPTURE, REFUND.
  • Key-value attributes are separated by semicolons (;).
  • Key-value pairs may appear in any order.
  • AUTHORIZE: Requires idempotency_key, amount, and currency. Generates a unique internal charge_id represented by ch_. Sets the initial authorized balance.
  • CAPTURE: Requires idempotency_key, charge_id, and amount. Captures up to the remaining authorized amount for that charge. Captured funds increase the merchant's settled balance.
  • REFUND: Requires idempotency_key, charge_id, and amount. Refunds up to the total captured amount for that charge. Refunded funds decrease the settled balance.

Business & Idempotency Rules

  1. Malformed Syntax: If a string lacks the pipe separator, has unknown actions, has missing mandatory fields, or contains a non-integer or non-positive amount (amount <= 0), reject the record with reason "MALFORMED".
  2. Idempotency:
    • If an idempotency_key is reused with identical parameters, return the identical previous outcome without re-applying financial mutations ("DUPLICATE").
    • If an idempotency_key is reused with different parameters, reject it with reason "IDEMPOTENCY_CONFLICT".
  3. State Invariants:
    • CAPTURE cannot capture more than the currently uncaptured authorized balance.
    • REFUND cannot refund more than the currently captured, unrefunded balance.
    • Any reference to an unknown or unauthorized charge_id must be rejected with reason "INVALID_CHARGE".
    • Over-capturing or over-refunding must be rejected with reason "INSUFFICIENT_FUNDS".

Interface

def process_payment_events(events: list[str]) -> dict:
    pass

Output Format

Return a dictionary with two keys:

  • "settled_balances": A dictionary mapping currency (e.g., "USD") to the total settled amount (integer cents).
  • "results": A list of strings, one per input event: "SUCCESS", "DUPLICATE", or "ERROR: ".

Example

events = [
    "AUTHORIZE|idempotency_key=k1;amount=1000;currency=usd",
    "CAPTURE|idempotency_key=k2;charge_id=ch_k1;amount=600",
    "CAPTURE|idempotency_key=k2;charge_id=ch_k1;amount=600",
    "REFUND|idempotency_key=k3;charge_id=ch_k1;amount=700",
    "REFUND|idempotency_key=k4;charge_id=ch_k1;amount=400"
]

result = process_payment_events(events)
# result == {
#     "settled_balances": {"usd": 200},
#     "results": [
#         "SUCCESS",
#         "SUCCESS",
#         "DUPLICATE",
#         "ERROR: INSUFFICIENT_FUNDS",
#         "SUCCESS"
#     ]
# }

Constraints

  • 1 <= len(events) <= 10000
  • 1 <= amount <= 10^9
  • Currencies are case-insensitive and should be normalized to lowercase.

Hints

Hint 1

Separate the parsing and syntax validation phase from the business execution phase to keep the code modular and easy to debug.

Hint 2

Store the full input payload alongside each idempotency key so you can distinguish an exact duplicate from an idempotency conflict.

Hint 3

Track charge state with a dedicated structure containing original currency, authorized amount, captured amount, and refunded amount.

Follow-up questions

  • How would you extend this design to support concurrent workers processing events for the same merchant?
  • If the event feed is an infinite stream rather than an in-memory list, how would you manage memory for idempotency key retention?
  • How would you support multi-currency conversions and fee deductions during capture and refund events?