Background
The Marketplace Payments team at a large online retailer pays third-party sellers every month. A nightly job uploads the ledger (sales, refunds and manual adjustments) to this service, and sellers open their payout statement in the seller portal. After the latest release, sellers say they can't see their own statements, the finance team's month-end numbers don't match, and the build is broken.
How the system works
Ledger file
The ledger is uploaded as CSV text, one id,sellerId,date,type,amount row per line:
id,sellerId,date,type,amount
E2,S-100,2026-09-01,SALE,125.60
E4,S-100,2026-09-20,REFUND,20.00
E5,S-100,2026-09-28,ADJUSTMENT,-15.25
- If the first non-blank line starts with
id(any case), it is a header and is skipped. Blank lines are ignored and every field is trimmed. - Each row has exactly 5 fields.
idandsellerIdmust not be empty. Seller ids are case-sensitive (S-100ands-100are different sellers). dateisYYYY-MM-DDand must be a real date.typeisSALE,REFUNDorADJUSTMENT(any case).amountis a money string: an optional leading-, whole units, and optionally.plus one or two digits (125.60,12.5,7,-15.25). No+, no thousands separators, no more than two decimals. A negative amount is negative in both its units and its cents:-3.25is −325 cents.SALEandREFUNDamounts must be greater than zero (a refund is written as a positive amount).ADJUSTMENTamounts may be positive or negative but not zero.- An id may appear only once per file.
- Any invalid row throws
InvalidRequestExceptionand rejects the whole file.
Importing
- Imports are all-or-nothing: if any row is invalid, or conflicts with a stored entry, nothing from that file is stored.
- An entry whose id is already stored with identical content is skipped. This makes retried uploads safe. The same id with different content rejects the whole file.
- The result reports
added(new entries stored) andskipped(identical repeats).
Marketplace fee
The fee is configured in basis points (payouts.fee-bps, default 800 = 8.00%). It is charged on each sale separately: fee = saleCents × feeBps / 10000, rounded half up to a whole cent. Refunds and adjustments carry no fee and get no fee back.
Payout statement
summarize(sellerId, from, to) covers entries dated from from (inclusive) to to (exclusive):
frommust be strictly beforeto, elseInvalidRequestException.- If the seller has no entries at all,
NotFoundException. A seller with entries, just none in the period, gets a statement of zeros. entryCount: entries in the period.salesCents: sum of SALE amounts.refundsCents: sum of REFUND amounts (a positive number).feesCents: sum of the per-sale fees.adjustmentsCents: sum of ADJUSTMENT amounts (signed).netCents = salesCents − refundsCents − feesCents + adjustmentsCents. It can be negative.
Example (fee 800 bps): sales 125.60 and 40.00, refund 20.00, adjustment −15.25 → sales 16560, fees 1005 + 320 = 1325, refunds 2000, adjustments −1525, net 11710.
API
| Method + path | Success | Notes |
|---|---|---|
POST /api/ledger/import (body: CSV, Content-Type: text/plain) | 200 + {"added","skipped"} | |
GET /api/sellers/{sellerId}/payout?from=YYYY-MM-DD&to=YYYY-MM-DD | 200 + {"sellerId","from","to","entryCount","salesCents","refundsCents","feesCents","adjustmentsCents","netCents"} | requires header X-Seller-Id |
Authorization: a seller may only read their own statement. The X-Seller-Id header must be present and exactly equal to {sellerId}; otherwise the response is 403.
Errors return {"error": "<CODE>", "message": "<detail>"}:
| Situation | HTTP status | error |
|---|---|---|
NotFoundException | 404 | NOT_FOUND |
missing or different X-Seller-Id | 403 | FORBIDDEN |
InvalidRequestException, or a missing/malformed from/to | 400 | INVALID_REQUEST |
Project structure
src/main/java/com/bugnik/payouts/
PayoutsApplication.java Spring Boot entry point
model/ EntryType, LedgerEntry, PayoutSummary, ImportResult
repository/ LedgerRepository (in-memory)
service/ LedgerService (imports), PayoutService (statements), FeeSchedule, exceptions
util/ AmountParser (money strings), LedgerCsvParser (ledger file)
web/ PayoutController, ApiError, ApiExceptionHandler
src/main/resources/application.properties fee configuration
src/test/java/... unit tests and MockMvc API tests
pom.xml Maven build (Spring Boot 3.3, Java 17)
Your task
Sellers and the finance team have reported problems since the last release:
- The project no longer builds.
- Sellers get "forbidden" when they open their own payout statement.
- Month-end statements pay sellers more than they should whenever a buyer was refunded.
- Some manual corrections from the payments team are booked for the wrong amount.
Find and fix the defects so that all tests pass.
- Do not modify anything in
src/test/orpom.xml. - Keep your changes minimal. Fix the defects; don't rewrite modules.
- Additional hidden tests check the behaviour described in this README.