Reported September 2026
Stripesimulation

Asynchronous Payment Event Processing

Reported by candidates from Stripe's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.

Get StealthCoderRuns invisibly during the live Stripe OA. Under 2s to a working solution.
Founder's read

Stripe sent this one out in September 2026, and the title sounds scarier than the work. Strip the payments story and it's a state machine with a dedup set, wrapped around CSV parsing. If you've got an OA invite for Stripe, expect a multi-part problem where each part layers a new rule onto the same function. Part 1 is aggregation, Part 2 is rejecting invalid transitions, and the rest build on that. Your real enemy is sloppy structure, not a hard algorithm. StealthCoder sits invisibly on your screen as a safety net if you blank mid-assessment, but the pattern below should get you most of the way.

The problem

Stripe handles millions of payments per day, amounting to billions of dollars of transactions. As you can imagine, this has a huge compute load as a consequence. Because of that, Stripe has been optimized to handle a lot of asynchronous events (think API calls). This allows us to distribute the load over time and leaves our architecture reliable against traffic spikes.
For this question, you will be building a small-scale system that can receive these events, parse and aggregate them into useful states and data. Events with the same event_id should not be processed again.
The question is broken into 4 parts, and we recommend you work on them in order one at a time.
Input (STDIN) For Custom Testing:
1 line with n, the number of events
n lines, each containing a single csv shaped string representing one event
Output:
One line per aggregated payment, with comma-separated values (no header row)
Column order:payment_id,merchant_id,state,amount_authorized,amount_captured,last_event_at
Numeric columns default to 0 when no value has been accumulated (e.g. payment_1,merchant_1,created,0,0,0)
Output payments in the order they were created (i.e., the order their create event was first processed)
Part 1 - Aggregating Payment States
You will be completing the function:
process_events(events: list[str]) -> list[str]
The function receives a list of events that need to be processed, in the order they were received. Each event has the following properties:
event_id: string: unique id of the event
event_time: int
event_type: string: payment | refund | merchant_update
payment_id: string: payment id of the event
payment_event_type: string: create| authorize | capture
merchant_id: string: merchant id associated to that payment event
amount: int
If an input property is null or empty it will be represented with a -. The output should not include - and leave no characters if the property is not present.
Aggregate all payment events in order to keep the most up-to-date information of a payment, having the correct state for it. For this part, you will only be working with payment event types.
A payment will only ever be attached to a single merchant.
For context, a basic payment has 2 stages: authorization and capture. Authorization for a card means that you, the buyer, have authorized the payment for your card, but no money has moved yet. A merchant (a.k.a. seller) can capture the payment later, and that is when the money actually moves.
A payment is considered partially_captured if a capture has been made, but the amount_captured < amount_authorized. A payment is considered successful after the full amount authorized has been captured, basically when amount_authorized == amount_captured. You can assume that the amount captured will never exceed the amount authorized.
The expected transition states between payment events:
Current Payment StateValid Events
Payment non-existentcreate
createdauthorize
authorizedauthorize, capture
partially_capturedcapture
successful-
For this part, you can assume that the events will always come in the correct order and multiple authorize and capture events can exist for the same payment.
After all events have been processed, we want the following string for each payment_id:
payment_id,merchant_id,state,amount_authorized,amount_captured,last_event_at
A payment event comes in the shape:
event_id,event_time,event_type,payment_id,payment_event_type,merchant_id,amount
merchant_id is only present on create events
amount is only present on authorize and capture events
Part 2 - Ignoring out-of-order events
Great, our system is processing millions of events. But now we have noticed a new issue: we are getting some events out-of-order of what a valid payment state should accept. For example, a payment that is in partially_captured should not accept an authorize event.
We need to make sure that only valid events are processed. events that are invalid should simply be ignored.
Current Payment StateValid Events
Payment non-existentcreate
createdauthorize
authorizedauthorize, capture
partially_capturedcapture
successful-
Part 3 - Processing refunds
Now that our payments events can handle the correct and incorrect states, we want to also support refunds.
Refunds come in via a separate event type and come in only with the payment_id as the event_data. After receiving a refund event only the state and the last_event_at properties change.
event_id,event_time,event_type,payment_id,payment_event_type,merchant_id,amount
Now the new acceptable state transitions are:
Payment StateValid Events
Payment non-existentcreate
createdauthorize
authorizedauthorize, capture
partially_capturedcapture
successfulrefund
refunded-
Part 4 - Receiving merchant events
In addition to payment and refund events, your system must now process merchant risk updates.
Each merchant has a risk score represented by an integer from 0 through 100, inclusive. A higher score indicates greater risk.
A merchant update uses the same CSV column order as the previous parts:
event_id,event_time,event_type,payment_id,payment_event_type,merchant_id,amount
For a merchant update:
event_type is merchant_update.
merchant_id identifies the merchant.
amount contains the merchant's risk score.
payment_id and payment_event_type are empty (-).
For example:
evt_1,0,merchant_update,-,-,merchant_1,95
In order to process the payments correctly, we want to flag high risk merchants, and block all new authorizations from being processed.
When a valid merchant_update event has a risk score greater than or equal to 80, the merchant becomes blocked.
Blocking takes effect immediately:
Ignore every authorize event received for that merchant after the merchant becomes blocked.
This applies whether the payment was created before or after the merchant became blocked.
Continue to accept create events for the merchant.
Continue to process valid capture and refund events. This allows payments authorized before the merchant was blocked to complete their lifecycle.
A merchant update does not directly change any existing payment's state, amounts, or last_event_at.
If an authorization is ignored for a payment in the created state, that payment remains created. Payments that were already authorized or partially captured keep their current state and can continue to receive valid capture events.
Once blocked, a merchant remains blocked for the rest of the input. A later merchant update with a risk score below 80 does not unblock the merchant.
FastPrep callable adapter
For the FastPrep callable adapter, implement processEvents with one events array parameter. The array contains the n event strings (n == events.length), so the separate count line used by custom STDIN is not passed to the function. Language-specific starter code provides the corresponding parameter type.

Function
processEvents(events: String[]) → String[]

Examples
Example 1
events = ["evt_1,0,payment,payment_1,create,merchant_1,-","evt_2,1,payment,payment_2,create,merchant_2,-","evt_3,2,payment,payment_1,authorize,-,10","evt_4,3,payment,payment_2,authorize,-,25","evt_5,4,payment,payment_1,capture,-,10","evt_6,5,payment,payment_2,capture,-,5"]
return = ["payment_1,merchant_1,successful,10,10,4","payment_2,merchant_2,partially_captured,25,5,5"]
Example 2
events = ["evt_1,0,payment,payment_1,create,merchant_1,-","evt_2,1,payment,payment_2,create,merchant_2,-","evt_3,2,payment,payment_1,authorize,-,10","evt_4,3,payment,payment_1,authorize,-,10","evt_5,4,payment,payment_2,authorize,-,15","evt_6,5,payment,payment_1,capture,-,10","evt_6,5,payment,payment_1,capture,-,10","evt_7,6,payment,payment_2,capture,-,15"]
return = ["payment_1,merchant_1,partially_captured,20,10,5","payment_2,merchant_2,successful,15,15,6"]
Note: The amount_captured is not updated twice since the last event has an ID that was already processed.
Example 3
events = ["evt_1,0,payment,payment_1,create,merchant_1,-","evt_2,1,payment,payment_2,capture,-,20","evt_3,2,payment,payment_1,capture,-,10","evt_4,3,payment,payment_2,create,merchant_2,-","evt_5,4,payment,payment_1,authorize,-,10","evt_6,5,payment,payment_2,authorize,-,20","evt_7,6,payment,payment_1,capture,-,10","evt_8,7,payment,payment_1,authorize,-,5","evt_9,8,payment,payment_2,capture,-,10","evt_10,9,payment,payment_2,capture,-,10"]
return = ["payment_1,merchant_1,successful,10,10,6","payment_2,merchant_2,successful,20,20,9"]
Note: evt_5 is ignored because a capture is not valid for a created payment. evt_6 is ignored because an authorize is not valid for a partially_captured payment.
Example 4
events = ["evt_1,0,payment,payment_1,create,merchant_1,-","evt_2,1,payment,payment_2,create,merchant_2,-","evt_3,2,payment,payment_1,authorize,-,10","evt_4,3,payment,payment_2,authorize,-,20","evt_5,4,payment,payment_1,capture,-,10","evt_6,5,payment,payment_2,capture,-,20","evt_7,6,refund,payment_1,-,-,-","evt_8,7,refund,payment_2,-,-,-","evt_9,8,refund,payment_1,-,-,-"]
return = ["payment_1,merchant_1,refunded,10,10,6","payment_2,merchant_2,refunded,20,20,7"]
Example 5
events = ["evt_1,0,payment,payment_1,create,merchant_1,-","evt_2,1,payment,payment_1,authorize,-,10","evt_3,2,merchant_update,-,-,merchant_1,85","evt_4,3,payment,payment_2,create,merchant_1,-","evt_5,4,payment,payment_2,authorize,-,20","evt_6,5,payment,payment_1,capture,-,10","evt_7,6,merchant_update,-,-,merchant_1,10"]
return = ["payment_1,merchant_1,successful,10,10,5","payment_2,merchant_1,created,0,0,3"]

Constraints
Events with the same event_id should not be processed again.
A payment will only ever be attached to a single merchant.
You can assume that the amount captured will never exceed the amount authorized.
Numeric columns default to 0 when no value has been accumulated (e.g. payment_1,merchant_1,created,0,0,0)
Output payments in the order they were created (i.e., the order their create event was first processed)
Each merchant has a risk score represented by an integer from 0 through 100, inclusive.
Once blocked, a merchant remains blocked for the rest of the input. A later merchant update with a risk score below 80 does not unblock the merchant.

Reported by candidates. Source: FastPrep

Pattern and pitfall

The problem reduces to a hash map keyed by payment_id plus a set of seen event_ids. Process events in order. Skip any event whose id is already in the set. Parse the CSV line, look up the payment, and check whether the event type is valid for the current state: create from nothing, authorize from created or authorized, capture from authorized or partially_captured. Update amounts, then recompute state by comparing captured to authorized. Track last_event_at from the event_time. Keep an insertion-ordered list of payment ids so output follows creation order. The pitfalls are small. The dash means empty, so parse amounts carefully. Output 0 for missing numbers. Don't mark an invalid event as seen unless the spec says so. Write a clean transition table now, because later parts reuse it. If the live OA throws a curveball and you freeze, StealthCoder can bail you out.

If you see this problem in your OA tomorrow, the play is to recognize the pattern in 30 seconds. StealthCoder buys you that recognition.

If this hits your live OA

You can drill Asynchronous Payment Event Processing cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Built by an Amazon engineer who passed his OA cold and still thinks the filter is broken.

Get StealthCoder

Related leaked OAs

⏵ The honest play

You've seen the question. Make sure you actually pass Stripe's OA.

Stripe reuses patterns across OAs. Built by an Amazon engineer who passed his OA cold and still thinks the filter is broken. Works on HackerRank, CodeSignal, CoderPad, and Karat.

Asynchronous Payment Event Processing FAQ

How hard is the Stripe Asynchronous Payment Event Processing OA really?+

Algorithmically it's easy. No graphs, no DP. The difficulty is volume: four parts, strict output formatting, and edge cases around state transitions. Candidates lose points on parsing details and ordering, not on clever logic. Clean code structure matters more than speed tricks.

What's the core trick?+

Use a dictionary of payment records plus a set of processed event_ids. Define valid transitions per state in one place. For each event, dedupe, validate against the current state, apply the update, then derive the new state from amount_authorized and amount_captured.

How do I handle the dash placeholder in the input?+

Treat a dash as empty when parsing each CSV field. Amount becomes 0 if absent, and merchant_id is only read on create events. Never print a dash in the output. Empty fields just leave nothing between the commas.

How do I keep the output in creation order?+

Store payments in an insertion-ordered structure, or keep a separate list of payment_ids appended when the create event is first processed. Python dicts preserve insertion order, so iterating the dict works. Don't sort by id or time.

How should I prepare in 48 hours?+

Write the full solution once from scratch for Part 1 and Part 2. Practice CSV splitting, a state transition table, and dedup by id. Then test edge cases: duplicate events, out-of-order capture, and a payment that reaches fully captured. Keep functions small so later parts slot in.

Problem reported by candidates from a real Online Assessment. Sourced from a publicly-available candidate-aggregated repository. Not affiliated with Stripe.

OA at Stripe?
Invisible during screen share
Get it